目录
HashSet是Set接口的子类,其内部采用了HashMap作为数据存储,HashSet其实就是在操作HashMap的key。HashSet是无序存储的,不能保证元素的顺序;HashSet并没有进行同步处理,因此是线程不安全的;HashSet可以存储null元素,但只能存储一个。
如下图:

观察上图:
- public HashSet() {
- map = new HashMap<>();
- }
总结:默认的无参构造,其底层会初始化一个空的HashMap,并使用默认初始容量16和负载因子0.75。
- public HashSet(Collection extends E> c) {
- map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
- addAll(c);
- }
总结:带集合参数的构造方法,底层使用默认的负载因子0.75和足以包含指定集合中所有怨怒是的初始容量来构造一个HashMap。
- public HashSet(int initialCapacity) {
- map = new HashMap<>(initialCapacity);
- }
总结:以指定初始容量的HashMap构造一个HashSet。
- public HashSet(int initialCapacity, float loadFactor) {
- map = new HashMap<>(initialCapacity, loadFactor);
- }
总结:构造一个具有初始容量和负载因子的HashMap。
add方法底层实际上是将该元素作为key添加到HashMap中,而PRESENT是作为默认的map的value值。如果添加的元素不存在,则加入到map中,并返回true。如果该元素已经存在,则返回false。
map的put方法在添加key-value对时,如果新放入HashMap的Entry中key与集合中原有的Entry的key相同,则新添加的Entry的value会覆盖原来Entry的value,但是key不会改变。因此,如果向HashSet中添加一个已经存在的元素时,新添加的集合元素不会被放入HashMap中,原有的元素也不会有任何改变,因此Set中的元素也就不会重复了
- public boolean add(E e) {
- return map.put(e, PRESENT)==null;
- }
如果给定的元素在HashSet中,则将其移除,其底层调用HashMap的remove()方法删除指定Entry。
- public boolean remove(Object o) {
- return map.remove(o)==PRESENT;
- }
返回HashSet中元素的个数
- public int size() {
- return map.size();
- }
判断HashSet是否为空
- public boolean isEmpty() {
- return map.isEmpty();
- }
如果HashSet中包含指定元素,则返回true。否则返回false
- public boolean contains(Object o) {
- return map.containsKey(o);
- }
在HashSet的源码中有这样一个构造函数:
- HashSet(int initialCapacity, float loadFactor, boolean dummy) {
- map = new LinkedHashMap<>(initialCapacity, loadFactor);
- }
可以看到该构造函数为包访问权限,不对外公开。该构造函数以指定的初始容量和负载因子构造一个新的空链表哈希集合 ,其实它是对LinkedHashSet的支持。我们可以看看LinkedHashSet的继承关系:

因此,在这里我们可以简单的对LinkedHashSet进行分析,它在实现了Set接口、Cloneable接口和Serializable接口的同时,也继承了HashSet。
LinkedHashSet的特点如下:
来看看LinkedHashSet的构造方法
- //使用指定的初始容量和负载因子构造一个新的哈希集合
- public LinkedHashSet(int initialCapacity, float loadFactor) {
- super(initialCapacity, loadFactor, true);
- }
-
- //使用指定的初始容量和负载因子构造新的哈希集合
- public LinkedHashSet(int initialCapacity) {
- super(initialCapacity, .75f, true);
- }
-
- //无参构造方法,默认容量16,负载因子0.75
- public LinkedHashSet() {
- super(16, .75f, true);
- }
-
- //基于集合构造一个带有指定元素的非空哈希集合
- public LinkedHashSet(Collection extends E> c) {
- super(Math.max(2*c.size(), 11), .75f, true);
- addAll(c);
- }
LinkedHashSet维护了一个hash表和双向链表,且通过head和tail分别指向链表的头和尾,每一个节点有before和after属性,这样可以形成双向链表。在添加一个元素时,先求hash值,再求索引,确定该元素在table表中的位置,然后将添加的元素加入到双向链表(如果该元素已经存在,则不添加)
(1)基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。
(2)当我们试图把某个类的对象当成 HashMap的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的equals(Object obj)方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。
(3)HashSet的其他操作都是基于HashMap的。
LinkedHashSet 底层使用 LinkedHashMap 来保存所有元素,它继承于 HashSet,其所有的方法操作上又与 HashSet 相同,因此 LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数,调用父类的构造器,底层构造一个 LinkedHashMap 来实现,在相关操作上与父类 HashSet 的操作相同,直接调用父类 HashSet 的方法即可。