Java中Set接口、各子类之间的介绍及区别

Set接口介绍

public interface Set<E> extends Collection<E>

一个不包含重复元素的 Collection接口的子接口。Set中的数据不包含满足 e1.equals(e2) 的元素,并且最多包含一个 null 元素。
Java中Set接口、各子类之间的介绍及区别_第1张图片

常用的Set接口的实现类

1. ConcurrentSkipListSet

根据Set中数据的自然排序或由创建时提供的比较器规则 ,根据使用的不同的构造方法,集合的元素将按照排序规则保持排序。
插入,删除和访问操作是线程安全的, size方法不是一个常规操作, 由于这些集合的异步性质,确定当前元素数量需要遍历元素,因此如果在遍历期间修改此集合,则可能会报告不准确的结果。 此外,批量操作addAll , removeAll , retainAll , containsAll , equals和toArray不保证能够执行时的原子性,换句话说,批量操作不是一个线程安全的操作。此类不允许使用null元素,因为null参数和返回值不能与不存在元素完全清晰的区分开。

※※ 自然排序

	public static void main(String[] args) throws IOException {
     
		Set<String> set = new ConcurrentSkipListSet<String>();
		set.add("B");
		set.add("A");
		System.out.println(set.toString()); //输出[A, B]
	}

※※ 带比较器的排序

	public static void main(String[] args) throws IOException {
     
		Set<String> set = new ConcurrentSkipListSet<String>((String x,String y)->{
     
			return x.compareTo(y) * -1;
		});
		set.add("A");
		set.add("C");
		set.add("B");
		System.out.println(set.toString());//输出 [C, B, A]
	}

2. CopyOnWriteArraySet

  • 内部使用CopyOnWriteArrayList所有操作,它们具有相同的基本属性。
  • 它最适合于集合大小通常保持较小,只读操作大大超过突变操作的应用程序,并且需要防止遍历期间线程之间的干扰。 它是线程安全的,允许所有元素,包括null 。
  • 可变操作(add , set , remove ,等)是昂贵的,因为它们通常意味着复制整个底层数组。
  • 迭代器不支持突变remove操作。
  • 遍历迭代器是快速的,不能遇到来自其他线程的干扰。 迭代器构建时迭代器依赖于数组的不变快照。

3. EnumSet

一个专门与枚举类型一起使用的Set实现。 Set中的所有元素都必须来自创建集合时明确或隐式指定的单个枚举类型。 这种表示非常紧凑和高效, 即使批量操作(如containsAll和retainAll )如果他们的参数也是枚举集也能很快运行。
由iterator返回的迭代器以自然顺序 (枚举枚举常量的声明顺序)遍历元素。 返回的迭代器是弱一致的 :它永远不会抛出ConcurrentModificationException ,它可能显示或可能不显示在迭代进行中发生的集合的任何修改的影响。

不允许使用null元素。 尝试插入一个null元素将抛出NullPointerException 。 尝试测试null元素的存在或删除一个会正常工作。

像大多数集合实现一样, EnumSet不是线程安全的。 如果多个线程同时访问枚举集,并且至少有一个线程修改该集合,则它应该在外部同步:
Set s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));

public enum MyEnum {
     
	
	NAME1("name1", 1), NAME2("name2", 2), NAME3("name3", 3), NAME4("name4", 4);
	public String getName() {
     
		return name;
	}

	public void setName(String name) {
     
		this.name = name;
	}

	public int getIndex() {
     
		return index;
	}

	public void setIndex(int index) {
     
		this.index = index;
	}

	private String name;
	private int index;
	
	private MyEnum(String name,int index) {
     
		this.name = name;
		this.index = index;
	}
	
	
}

	public static void main(String[] args) throws IOException {
     
		Set<MyEnum> set = EnumSet.allOf(MyEnum.class);
		Iterator<MyEnum> iterable = set.iterator();
		while (iterable.hasNext()) {
     
			MyEnum myEnum = iterable.next();
			System.out.println(myEnum.getName()); //name1 name2 name3 name4
		}
	}

4. HashSet

此类由哈希表(实际为HashMap实例)支持。 对集合的迭代顺序不作任何保证; 特别是,它不能保证在一段时间内对集合数据访问其顺序保持不变。 这个类允许null元素

这个类提供了固定的时间性能的基本操作(add,remove,contains和size)。 迭代此集合需要与HashSet实例的大小(元素数量)和后台HashMap实例(桶数)的“容量”的总和成比例的时间。 因此,如果迭代性能很重要,不要将初始容量设置得太高(或负载因子太低)是非常重要的。

请注意,HashSet不是线程安全的。 如果多个线程并发访问哈希集,并且至少有一个线程修改该集合,那么它必须在外部进行同步:
Set s = Collections.synchronizedSet(new HashSet(…));

该类iterator方法返回的迭代器是故障快速的 :如果集合在迭代器创建之后的任何时间内被修改,除了通过迭代器自己的remove方法之外,迭代器会抛出一个ConcurrentModificationException 。
可以参考:解读阿里巴巴Java手册:为什么在foreach中禁止add/remove操作

5. LinkedHashSet

哈希表和链表实现了Set接口,具有可预测的迭代次序。 这种实现不同于HashSet,它内部维护一个所有条目的运行双向链表。 该链表定义了将元素插入集合(插入顺序 ) 的顺序 。 请注意,如果一个元件被重新插入到组插入顺序不受影响 。 (元件e重新插入一组s如果当s.contains(e)将返回true之前立即调用s.add(e)被调用。)

该类提供了所有可选的Set操作,并允许null元素。 像HashSet一样,它提供了稳定的基本操作(add,contains和remove)。 由于维护链表的额外费用性能可能略低于HashSet ,但有一个例外:无论其容量如何LinkedHashSet的迭代需要与集合的大小成比例的时间, HashSet的迭代可能更昂贵,需要与其容量成比例的时间。

链接哈希集具有影响其性能的两个参数: 初始容量和负载因子 。 但是请注意,为初始容量选择非常高的值是该类比HashSet性能影响不太严重的,因为迭代次数对于这个类不受容量影响。
可参考:解读阿里巴巴Java手册:为什么HashMap初始化需要设定大小,HashMap初始化大小设定多少合适

请注意,它不是线程安全的。 如果多个线程同时访问链接的散列集,并且至少有一个线程修改该集合,那么它必须在外部进行同步:

Set s = Collections.synchronizedSet(new LinkedHashSet(…));
该类iterator方法返回的迭代器是故障快速的 :如果在创建迭代器之后的任何时间对该集合进行了修改,除了通过迭代器自己的remove方法之外,迭代器将会抛出一个ConcurrentModificationException 。

6. TreeSet

TreeMap与 ConcurrentSkipListSet类似。 内部元素使用其自然排序或由集合创建时提供比较器进行排序。

需要注意的是由一组(无论是否提供了明确的比较器)保持的顺序必须与equals一致 ,如果它是要正确实现Set接口。 (参见Comparable或Comparator为一致的精确定义与equals)。这是因为该Set接口在来定义equals的操作,但一个TreeSet例如使用其执行所有元件比较compareTo (或compare )方法,于是两个通过该方法认为相等的元素从集合的角度来看是相等的。 集合的行为是明确定义的,即使其排序与equals不一致; 它只是没有遵守Set界面的总体合同。

请注意,TreeSet并不是线程安全的,并且不允许Null元素。 如果多个线程并发访问树,并且至少有一个线程修改该集合,则必须在外部进行同步:
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(…));

该类iterator方法返回的迭代器是故障快速的 :如果在迭代器创建之后的任何时间对该集合进行了修改,除了通过迭代器自己的remove方法之外,迭代器将抛出一个ConcurrentModificationException 。

总结

Set 线程 元素
ConcurrentSkipListSet 插入,删除和访问操作是线程安全的 不允许NULL
CopyOnWriteArraySet 线程安全 允许NULL
EnumSet 不安全 不允许NULL
HashSet 不安全 允许NULL
LinkedHashSet 不安全 允许NULL
TreeSet 不安全 不允许NULL

你可能感兴趣的:(java,java,数据结构)