HashSet 源碼分析

本文將從如下幾個方面介紹

前言java

HashSet 的特色數組

類圖安全

源碼分析多線程

HashSet 如何保證元素的不重複併發

總結app

前言

在工做中,常常有這樣的需求,須要判斷某個ID是否在某個組的管理之下等,就須要查詢該組下的ID放到一個集合中,且集合中元素不能有重複,以後判斷該集合是否包含咱們的目標ID;這時,咱們可使用 HashSet 來存放咱們的ID,HashSet能夠自動的幫助咱們去重,好比HashSet<String> set = new HashSet<>(list) 等。接下來看下 HashSet 的內部是怎麼實現的。源碼分析

HashSet的特色

從 HashSet 的 Javadoc 的說明中,能夠獲得如下信息:this

1. HashSet 底層是使用 HashMap 來保存元素的spa

2.它不保證集合中存放元素的順序,便是無序的,且這種順序可能會隨着時間的推移還會改變.net

3.容許 null 值,且只有一個

4.HashSet 不是線程安全的,底層的 HashMap 不是線程安全的,它天然就不是啦,可使用 Collections.synchronizedSet(new HashSet()) 來建立線程安全的 HashSet;synchronizedSet 方法底層會根據 Set 來建立 SynchronizedSet 對象,而 SynchronizedSet 類繼承了 SynchronizedCollection,在 SynchronizedCollection 中的全部方法都加上了 synchronized 關鍵字,因此它是線程安全的,能夠被多線程併發訪問。

5.集合中的元素不會重複

類圖

先來看看 HashSet 的一個類圖

從類圖中,能夠看到,  HashSet 繼承了 AbstractSet 抽象類, 而 AbstractSet 又繼承了 AbstractCollection 抽象類,此外,HashSet 還實現了 Set 接口等。

AbstractSet 抽象類主要實現了兩個方法 equals 和 hashcode 方法,由於 HashSet  中沒有重複元素,就是根據這兩個方法來進行判斷的:

public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Set))
            return false;
        Collection<?> c = (Collection<?>) o;
        if (c.size() != size())
            return false;
        try {
            return containsAll(c);
        } catch (ClassCastException unused)   {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }
    }

    public int hashCode() {
        int h = 0;
        Iterator<E> i = iterator();
        while (i.hasNext()) {
            E obj = i.next();
            if (obj != null)
                h += obj.hashCode();
        }
        return h;
    }

Set 接口,它是一個頂層接口,主要定義了一些公共的方法,如 add,  isEmpty,  size,  remove,  contains 等一些方法;HashSet, SortedSet,TreeSet 都實現了該接口。

源碼分析

接下來看下它的內部實現,它內部使用 HashMap 來存放元素,它的全部方法基本上都是調用 HashMap 的方法來實現的,相等於對HashMap包裝了一層,關於 HashMap 的實現,能夠參考 HashMap源碼分析-jdk1.6和jdk1.8的區別

public class HashSet<E>  extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable
{
    // 用來存放元素的 HashMap
    private transient HashMap<E,Object> map;

    // 由於 HashMap 是存放鍵值對的,而 HashSet 只會存放其中的key,即當作 HashMap 的key,
    // 而value 就是這個 Object 對象了,HashMap 中全部元素的 value 都是它
    private static final Object PRESENT = new Object();

    // 無參構造,建立 HashMap 對象,初始大小爲 16
    public HashSet() {
        map = new HashMap<>();
    }

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

    // 根據初始大小和加載因子建立 HashSet
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

    // 根據初始大小建立 HashSet
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

    // 迭代器
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }
........................
}

從上面聲明可看到,HashSet 底層是使用 HashMap 來存放元素的,且 HashMap 中全部元素的 value 都是同一個 Object 對象,且它被 final 修飾。

接下來看下它的方法實現:

// 返回集合中元素的個數
    public int size() {
        return map.size();
    }

    // 判斷集合是否爲空
    public boolean isEmpty() {
        return map.isEmpty();
    }

    // 集合中是否包含某個值,即判斷 HashMap 中是否包含該key
    public boolean contains(Object o) {
        return map.containsKey(o);
    }

    // 添加元素,在這裏能夠看到,添加元素的時候,會向 HashMap 中添加,且 HashMap中的value都是同一個 Object對象
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

    // 刪除元素
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

    // 清楚集合
    public void clear() {
        map.clear();
    }

以上就是 HashSet 源碼的所有實現了,看着很簡單,可是要知道 HashMap 的實現過程纔會清楚。

HashSet 如何保證元素的不重複

接下來,看下 HashSet 的 add 方法,看下它是如何保證添加的元素不重複的

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

以後來看下 HashMap 的 put 方法:

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

put 方法會調用 putVal 方法進行添加元素,來看下 putVal 方法的實現:

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;
        // 若是 key 的 hashcode 在 Node 數組中不存在,即 集合中沒有改元素,則建立 Node 節點,加入到 Node 數組中,添加元素成功
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            // 若是在 Node 數組中該 key ,則在 Node 數組該索引上的鏈表進行查找
            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) {
                        // 在該鏈表上找不到該key,則建立,插入到鏈表上
                        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;
                }
            }
            // 若是 key 已存在,則返回舊的值
            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);
        // 若是元素不存在,則返回 null
        return null;
    }

因此,在向 HashSet 添加元素的時候,若是要添加元素的 hashcode 已存在,且 equals 相等,則會替換掉舊的值。

關於 HashMap 的其餘方法實現,能夠參考 HashMap源碼分析-jdk1.6和jdk1.8的區別

以上就是 HashSet 的實現。看起來很簡單,可是前提是得知道 HashMap 的實現。

 總結

HashSet的特色

1. HashSet 底層是使用 HashMap 來保存元素的

2.它不保證集合中存放元素的順序,便是無序的,且這種順序可能會隨着時間的推移還會改變

3.容許 null 值,且只有一個

4.HashSet 不是線程安全的,底層的 HashMap 不是線程安全的,它天然就不是啦,可使用 Collections.synchronizedSet(new HashSet()) 來建立線程安全的 HashSet

5.集合中的元素不會重複

相關文章
相關標籤/搜索