HashSet底層原理詳解


HashSet底層原理詳解

1. 說明

  1. HashSet實現了Set接口
  2. HashSet底層實質上是HashMap
  3. 可以存放null值,但是只能有一個null
  4. HashSet不保證元素是有序的,取決於hash后,再確定索引的結果,即不保證存放元素的順序和取出順序一致
  5. 不能有重復元素/對象

2. 底層機制說明

HashSet底層是HashMap,HashMap底層是(數組+鏈表+紅黑樹)
  1. 先獲取元素的哈希值(hashcode方法)
  2. 對哈希值進行運算,得出一個索引值即為要存放在哈希表中的位置號
  3. 如果該位置上沒有其他元素,則直接存放,如果該位置上有其他元素,則需要進行equals判斷,如果相等,則不再添加,如果不相等,則以鏈表的方式添加
  4. Java8以后,如果一條鏈表中的元素個數到達TREEIFY_THRESHOLD(默認是8),並且table的大小>=MIN_TREEIFY_CAPACITY(默認64),就會進行數化(紅黑樹)
class HashSetSource {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        //第 1 次 add
        hashSet.add("java");
        //第 2 次 add
        hashSet.add("php");
        hashSet.add("java");
        System.out.println("set=" + hashSet);

        /**
         * 對 HashSet 的源碼解讀
         *
         *         1. 執行 HashSet()
         *         public HashSet() {
         *             map = new HashMap<>();
         *         }
         *
         *         2. 執行 add()
         *         public boolean add(E e) {//e = "java"
         *             return map.put(e, PRESENT)==null;
         *             //(static) PRESENT = new Object();
         *         }
         *
         *         3.執行 put() , 該方法會執行 hash(key) 得到 key 對應的 hash 值 
         *         根據算法 h = key.hashCode()) ^ (h >>> 16)
         *         public V put(K key, V value) {//key = "java" value = PRESENT 共享
         *             return putVal(hash(key), key, value, false, true);
         *         }
         *
         *         4.執行 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; //定義了輔助變量
         *             //table 就是 HashMap 的一個數組,類型是 Node[]
         *             //if 語句表示如果當前 table 是 null, 或者 大小=0
         *             //就是第一次擴容,到 16 個空間.
         *             if ((tab = table) == null || (n = tab.length) == 0)
         *             n = (tab = resize()).length;
         *
         *         //(1)根據 key,得到 hash 去計算該 key 應該存放到 table 表的哪個索引位置,並把這個位置的對象,賦給 p
         *         //(2)判斷 p 是否為 null
         *         //(2.1) 如果 p 為 null, 表示還沒有存放元素, 就創建一個 Node (key="java",value=PRESENT)
         *         //(2.2) 就放在該位置 tab[i] = newNode(hash, key, value, null)
         *             if ((p = tab[i = (n - 1) & hash]) == null)
         *             tab[i] = newNode(hash, key, value, null);
         *             else {
         *
         *         //一個開發技巧提示: 在需要局部變量(輔助變量)時候,在創建
         *
         *             Node<K,V> e; K k; 
         *
         *         //如果當前索引位置對應的鏈表的第一個元素和准備添加的 key 的 hash 值一樣
         *         //並且滿足 下面兩個條件之一:
         *
         *         //(1) 准備加入的 key 和 p 指向的 Node 結點的 key 是同一個對象
         *         //(2) p 指向的 Node 結點的 key 的 equals() 和准備加入的 key 比較后相同
         *         //就不能加入
         *
         *             if (p.hash == hash &&
         *             ((k = p.key) == key || (key != null && key.equals(k))))
         *             e = p;
         *
         *         //再判斷 p 是不是一顆紅黑樹,
         *         //如果是一顆紅黑樹,就調用 putTreeVal , 來進行添加
         *
         *             else if (p instanceof TreeNode)
         *             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
         *             else {
         *
         *             //如果 table 對應索引位置,已經是一個鏈表, 就使用 for 循環比較
         *
         *             //(1) 依次和該鏈表的每一個元素比較后,都不相同, 則加入到該鏈表的最后
         *             // 注意在把元素添加到鏈表后,立即判斷 該鏈表是否已經達到 8 個結點
         *             // , 就調用 treeifyBin() 對當前這個鏈表進行樹化(轉成紅黑樹)
         *             // 注意,在轉成紅黑樹時,要進行判斷, 判斷條件
         *
         *             // if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
         *             // resize();
         *
         *             // 如果上面條件成立,先 table 擴容.
         *             // 只有上面條件不成立時,才進行轉成紅黑樹
         *
         *             //(2) 依次和該鏈表的每一個元素比較過程中,如果有相同情況,就直接 break

         */

    }
}

3. 分析HashSet的擴容和轉成紅黑樹機制

  1. HashSet底層是HashMap,第一次添加時,table的數組擴容到16,臨界值(threshold)是16 * 加載因子(loadFactor是0.75)=12
  2. 如果table數組使用到了臨界值12,就會擴容到16 * 2 = 32,新的臨界值就是32 * 0.75 = 24,依次類推
  3. Java8以后,如果一條鏈表中的元素個數到達TREEIFY_THRESHOLD(默認是8),並且table的大小>=MIN_TREEIFY_CAPACITY(默認64),就會進行數化(紅黑樹),否則仍然采用數組擴容機制


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM