Collection - Set
Set 简介
Set 注重独一无二的性质,该体系集合用于存储无序(存入和取出的顺序不一定相同)元素,不允许有重复的元素。对象的相等性本质是对象的 hashCode 值(Java 是依据对象的内存地址计算出的此序号)判断的,如果想要让两个不同的对象视为相等的,就必须覆盖 Object 的 hashCode() 方法和 equals() 方法。

- Set 继承了 Collection 的接口。实际上,Set 就是一个 Collection,只是行为不同(这是继承和多态思想的典型应用,表现不同的行为);
 - SortedSet 继承了 Set 的接口。SortedSet 中的内容是排序的唯一值,排序的方法是通过比较器(Comparator);
 - NavigableSet 继承了 SortedSet 的接口。它提供了丰富的查找方法:如"获取大于/等于某值的元素"、“获取小于/等于某值的元素”等等;
 - AbstractSet 是一个抽象类,它继承于 AbstractCollection,AbstractCollection 实现了 Set 中的绝大部分方法,为实现 Set 的实例类提供了便利;
 - HashSet 类依赖于 HashMap,它实际上是通过 HashMap 实现的。HashSet 使用散列函数存储元素,元素是无序的、散列的;
 - TreeSet 类依赖于 TreeMap,它实际上是通过 TreeMap 实现的。TreeSet 将元素存储在红-黑树数据结构中,元素是有序的,它是按自然排序或者用户指定比较器排序的 Set;
 - LinkedHashSet 是按插入顺序排序的 Set;
 - EnumSet 是只能存放 Emum 枚举类型的 Set。
 

HashSet
HashSet 是对 HashMap 的简单包装,对 HashSet 的函数调用都会转换成合适的 HashMap 方法。
package java.util;
public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
	......
	private transient HashMap<E,Object> map; //HashSet里面有一个HashMap
    // PRESENT 是用于关联 map 中当前操作元素的一个虚拟值
    private static final Object PRESENT = new Object();
    
    public HashSet() {
        map = new HashMap<>();
    }
    
    ......
    public boolean add(E e) { //转换成 map 方法
        return map.put(e, PRESENT)==null;
    }
    ......
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- HashSet 中维护了一个 HashMap 对象 
map,HashSet 的重要方法,如add()、remove()、iterator()、clear()、size()等都是围绕 map 实现的。 - HashSet 类中通过定义 
writeObject()和readObject()方法确定了其序列化和反序列化的机制。 PRESENT是用于关联 map 中当前操作元素的一个虚拟值。
哈希表边存放的是哈希值。HashSet 存储元素的顺序并不是按照存入时的顺序(和 List 显然不同) 而是按照哈希值来存的,所以取数据也是按照哈希值取的。元素的哈希值是通过元素的 hashCode() 方法来获取的,HashSet 首先判断两个元素的哈希值,如果哈希值一样,接着会比较 equals() 方法。如果 equls() 结果为 true ,HashSet 就视为同一个元素;如果 equals() 为 false 就不是同一个元素。
哈希值相同 equals() 为 false 的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存在一列中。
HashSet 通过 hashCode 值来确定元素在内存中的位置。一个 hashCode 位置上可以存放多个元素。
LinkedHashSet
LinkedHashSet 是按插入顺序排序的 Set。
package java.util;
public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
    
    ...
    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }
    
    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }
    
    public LinkedHashSet() {
        super(16, .75f, true);
    }
    ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
LinkedHashSet 内的个构造方法都是调用父类 HashSet 的构造方法。需要强调的是:LinkedHashSet 构造方法实际上调用的是父类 HashSet 的非 public 构造方法。
// java.util.HashSet
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}
2
3
4
5
6
7
8
不同于 HashSet 内 public 构造方法中初始化的 HashMap 实例,这个构造方法中,初始化了 LinkedHashMap 实例。
也就是说,实际上,LinkedHashSet 维护了一个双链表。由双链表的特性可以知道,它是按照元素的插入顺序保存的。所以,这就是 LinkedHashSet 中存储的元素是按照插入顺序保存的原理。
参考资料
- https://dunwu.github.io/javacore/container/java-container-set.html