HashSet原始碼分析

羅立發表於2019-05-02

HashSet是一個HashMap的一個例項,它不保證它的元素們的相對順序始終是一樣的。它也允許null元素的存在。和其他的集合一樣,它也是執行緒不安全,具有fail-fast機制的。

private transient HashMap<E,Object> map;

// 這裡構造了一個虛擬的物件來充當HashMap中的value值。
//這個物件是用final修飾的,所以這個value值也是保持不變的
private static final Object PRESENT = new Object();
複製程式碼

上面是它的2個屬性。

public HashSet() {
    map = new HashMap<>();
}

public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}

public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}

複製程式碼

上面是4種構造本類例項的方法。

public Iterator<E> iterator() {
    return map.keySet().iterator();
}
複製程式碼

HashSet的迭代器也是來自HashMap。從這裡可以看出HashSet中的元素其實是HashMap中的key的集合,因為該迭代器就是遍歷的HashMap的key集合。

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}複製程式碼

上面是他的add方法。從這裡也可以看出value就是用這個虛擬的物件來充當的。

其他的方法也都是基於HashMap來實現的。


另外,HashSet是怎麼保證元素不重複的呢?因為它的add方法其實就是HashMap的put方法實現的,所以它的這個性質也是由這個方法來保證的。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;

        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}複製程式碼

上面就是底層方法的實現,說明下期中一些變數的作用。p節點是用來接收集合中的第一個元素以及利用它來呼叫next來遍歷集合中的其他元素,而e節點則是來接收p節點。用K k來接收集合中元素的key值。

if (p.hash == hash &&
    ((k = p.key) == key || (key != null && key.equals(k))))
    e = p;複製程式碼

從上面這段程式碼可以看出先是比較集合中的元素的hash值是否與要新增的元素的hash值相等,以及他們的key是否相等(此處是用==來判斷的);然後用元素的equals方法比較key值是否相等。如果條件成立則把p節點賦給e節點,此時說明要加入的元素的hash值與集合中的一個元素的hash值相等了,也就是說對於key有了相同的對映。今兒判斷e是否為null,若不為null,就把集合中的這個元素的value值賦給oldValue作為返回值,然後把要新增的元素的value值賦給e節點。


從上面的分析可以看出它是怎麼保證元素的不重複的性質,也知道了其實HashMap本身的元素們也是具有不重複的性質的。

Set<String> set = new HashSet();
set.add(null);
set.add("null");
System.out.println(set);//[null, null]複製程式碼

最後發現了一個有趣的地方。


相關文章