
3.4 Set
Set是一个接口,这个接口约定了在其中的数据是不能重复的,它有许多不同的实现类,图3-15给出了常用的Set的实现类。

图3-15 Set类图
这一节重点介绍其中的三个:HashSet、LinkedHashSet和TreeSet。
3.4.1 HashSet
在介绍HashSet之前,首先需要理解HashSet的两个重要的特性:
1)HashSet中不会有重复的元素;
2)HashSet中最多只允许有一个null。
显然HashMap也有着相同的特性:HashMap的key不能有重复的元素,key最多也只能有一个null。正因为如此,HashSet内部是通过HashMap来实现的。只不过对于HashMap来说,每个key可以有自己的value;而在HashSet中,由于只关心key的值,因此所有的key都会使用相同的value(PRESENT)。由于PRESENT被定义为static,因此会被所有的对象共享,这样的实现显然会节约空间。
需要注意的是:
1)HashSet不是线程安全的,如果想使用线程安全的Set,那么可以使用CopyOnWriteArraySet、Collections.synchronizedSet(Set set)、ConcurrentSkipListSet和Collections.newSetFromMap(NewConcur-rentHashMap)。
2)HashSet不会维护数据插入的顺序,如果想维护插入顺序,那么可以使用LinkedHashSet。
3)HashSet也不会对数据进行排序,如果想对数据进行排序,那么可以使用TreeSet。
HashSet的使用示例代码如下:

从运行结果可以看出,HashSet中的数据是无序的。
3.4.2 LinkedHashSet
LinkedHashSet是HashSet的扩展,HashSet并不维护数据的顺序,而LinkedHashSet维护了数据插入的顺序。HashSet在内部是使用HashMap来实现的,而LinkedHashSet内部通过LinkedHashMap来实现。
示例代码如下:

从运行结果可以看出LinkedHashSet维护了数据插入的顺序。
3.4.3 TreeSet
TreeSet不仅有HashSet所有的特性,而且它还增加了一个排序的特性。也就是说TreeSet中的数据是有序的,它默认使用的是数据的自然顺序,当然在创建TreeSet的时候也可以指定Comparator来对数据进行排序。那么TreeSet底层是如何实现数据排序的,下面给出TreeSet内部实现的部分源码:

通过源码可以发现,它的实现与HashMap类似,底层使用TreeMap来存储数据,因此把数据有序功能的实现交给了TreeMap。这里重点介绍一下add方法。对于TreeMap而言,它的返回值有两种情况:
1)如果新增加的key是唯一的,那么它会返回null;
2)如果新增加的key在TreeMap中已经存在了,那么它会返回key对应的value值。
因此TreeSet的add方法正是通过这个返回值来判断新的数据是否被加入进去:如果put方法返回null,那么说明数据被插入到TreeSet中了,此时map.put(e, PRESENT)==null的值为true,因此add方法返回true。否则返回false表示数据已经在TreeSet中了,不需要再次插入了。
对于其他的方法而言,它们的实现与HashSet类似,交给底层TreeMap来实现了。
示例代码如下:

从运行结果可以看出,TreeMap维护了数据的顺序。