Map集合簡單分析

Map集合

小白以爲請各位多多關照,有什麼不對的還請提出來,謝謝

底層採用哈希表(動態數組 +鏈表(或者紅黑樹))
數組的動態數組保證
鏈表到 紅黑樹的相互保證
存儲的是一個K,V對象 每一個都是 map.Entry 對象
在這裏插入圖片描述

 /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
初始值等於 16 
/**
   * The load factor used when none specified in constructor.
   */
  static final float DEFAULT_LOAD_FACTOR = 0.75f;
做動態數組擴容負載因子
    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 {
        ........
        }
  • 添加數據
    當我們第一次put的時候如果沒有被初始化,就先初始化,初始化化之後拿到數組的長度值減一,與當前hash值做&運算,得到一個下標,如果當前這個下標等於空
    ,這時直接創建鏈表nod值就好了,
    當存放的是nod的key 與hash 碰撞了,equals不等,他會遍歷val值沒有重複的就直接掛在鏈表最末尾上(我寫不太好 看人家的)
    鏈接: https://www.pianshen.com/article/1198306081/.
  • 數組動態擴容
    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表爲空或者長度爲0,就進行創建,即resize方法
        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;
            //如果當前桶有值,且當前桶的key的hsahCode和寫入的key相等,就賦值給e,直接覆蓋value.
            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;
                    }
                    //如果當前key和要插入的key相同,則跳出循環
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //如果e!=null,說明key相同,則直接覆蓋value
            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;
    }

具體的流程可以概括一下:

  • 判斷當前桶是否爲空,空的就需要初始化(resize 中會判斷是否進行初始化)。
  • 根據當前 key 的 hashcode 定位到具體的桶中並判斷是否爲空,爲空表明沒有 Hash 衝突就直接在當前位置創建一個新桶即可。
  • 如果當前桶有值( Hash 衝突),那麼就要比較當前桶中的 key、key 的 * * * hashcode 與寫入的 key 是否相等,相等就賦值給 e,在第 8 步的時候會統一進行賦值及返回。
  • 如果當前桶爲紅黑樹,那就要按照紅黑樹的方式寫入數據。
  • 如果是個鏈表,就需要將當前的 key、value 封裝成一個新節點寫入到當前桶的後面(形成鏈表)。
  • 接着判斷當前鏈表的大小是否大於預設的閾值,大於時就要轉換爲紅黑樹。
  • 如果在遍歷過程中找到 key 相同時直接退出遍歷。
  • 如果 e != null 就相當於存在相同的 key,那就需要將值覆蓋。
  • 最後判斷是否需要進行擴容。

① 如果該位置沒有數據,用該數據新生成一個節點保存新數據,返回null;

② 如果該位置有數據是一個紅黑樹,那麼執行相應的插入 / 更新操作;

③ 如果該位置有數據是一個鏈表,分兩種情況一是該鏈表沒有這個節點,另一個是該鏈表上有這個節點,注意這裏判斷的依據是key.hash是否一樣:

如果該鏈表沒有這個節點,那麼採用尾插法新增節點保存新數據,返回null;如果該鏈表已經有這個節點了,那麼找到該節點並更新新數據,返回老數據。

  • get數據時
final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //如果table已經初始化,長度大於0,且根據hash尋找table中的項也不爲空
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            //桶中第一個元素命中,直接返回
            if (first.hash == hash && 
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            //如果桶中不止一個節點
            if ((e = first.next) != null) {
                //如果是紅黑樹,就在紅黑樹中尋找
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                //否則就在鏈表中尋找
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
  • 先判斷key hash如果爲空 返回null
  • 如果直接命中直接返回,
  • 要是裏面不至一個節點,判斷紅黑樹 還是鏈表
  • 是紅黑樹就按照紅黑樹查找,鏈表就按照鏈表的方式查找

在resize方法中:

hashmap中的鍵值對大於閥值時或者初始化時,就調用resize方法進行擴容;
每次擴展的時候,都是擴展2倍;
擴展後Node對象的位置要麼在原位置,要麼移動到原偏移量兩倍的位置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章