1. 程式人生 > >基礎知識(一) HashMap 原始碼詳解

基礎知識(一) HashMap 原始碼詳解

因為最近想面試,所以複習下。分析學習基於JDK1.8 HashMap 繼承於 AbstrackHashMap 實現於 Map<K,V>, Cloneable, Serializable,內部使用雜湊連結串列 紅黑樹實現。注意此Map不是執行緒安全的,如果需要同步使用請使用ConcurrentHashMap 或者 Collections.synchronizedMap 常量引數 1、下面的都是直接static final 的值,也就是在JVM準備的時候就已經初始化了 DEFAULT_INITIAL_CAPACITY =16 預設容量為 MAXIMUM_CAPACITY =1 << 30 最大容量為 DEFAULT_LOAD_FACTOR = 0.75f 預設負載因子 TREEIFY_THRESHOLD=8 連結串列轉換紅黑樹的閥值 UNTREEIFY_THRESHOLD=6 紅黑樹轉換連結串列的閥值 MIN_TREEIFY_CAPACITY=64 桶中bin最小hash容量,如果大於這個值會進行resize擴容操作,此值至少是TREEIFY_THRESHOLD的4倍 2、下面說下成員變數 都是 transient,也就是說不會被序列化的欄位 Node<K,V>[] table  HashMap內部類實現了Map的內部類Entry,用於儲存K,V,第一次使用的時候被建立,根據需要可以進行resize。分配長度為2的冥次方 Set<Map.Entry<K,V>> entrySet   當被呼叫entrySet時被賦值。通過keySet()方法可以得到map key的集合,通過values方法可以得到map value的集合 int size 存放在map中K,V的總數 int modCount  HashMap被結構性修改的次數。(結構性修改是指改變了KV對映數量的操作或者修改了HashMap的內部結構(如 rehash)。這個用於fail-fast int threshold  進行resize的閥值,當Map中K,V數量(size) 超過了這個值,那將進行resize操作。 threshold  =DEFAULT_INITIAL_CAPACITY*DEFAULT_LOAD_FACTOR final float loadFactor 負載因子 構造方法
目前有4個構造方法 1、public HashMap(int initialCapacity, float loadFactor) 引數為初始容量,負載因子 步驟: 1、如果容量引數小於0就丟擲異常 2、如果容量引數大於最大值MAXIMUM_CAPACITY 就初始化為MAXIMUM_CAPACITY 3、如果負載因子小於等於0或者是一個非數字就丟擲異常 4、賦值負載因子 5、使用tableSizeFor 計算初始容量 方法原始碼解析 先看put方法,使用的是 publicV put(Kkey, Vvalue) { returnputVal(hash(key),key,value,false
,true);     }   內建final方法,不可被子類重寫,在編譯期已經靜態繫結 finalVputVal(inthash, Kkey, Vvalue,booleanonlyIfAbsent,booleanevict 引數: hash 計算出的hash值 key  傳入鍵 value 傳入值 onlyIfAbsent 預設false,為true的時候不覆蓋已存在的值 evict 預設true
transient Node<K,V>[] table;
static final int TREEIFY_THRESHOLD = 8;
transient int modCount;
transient int size;
int threshold;
//新手看到這麼多變數肯定暈了,記住一點,這是引用傳遞
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 賦值給區域性變數tab 並判斷其是否為空或size為0,如果為空則進行table初始化,並將初始化的size賦值給n(初始化其實就是給threshold計算出一個閥值,並new了一個node給table)
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //根據hash值計算出tab陣列的位置並判斷其是否為空,如果為空就直接把值存入一個新的Node中
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        //如果計算出的p 索引有元素存在
        else {
            Node<K,V> e; K k;
            //根據hash key判斷是不是相同的元素,如果是相同的元素就把p(老元素) 賦值給新的成員變數e
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //如果不是相同的key,只是index一樣,判斷元素是不是紅黑樹,是就將他放入樹中
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //不是一樣的key hash 並且不是紅黑樹
            else {
                 //無限迴圈
                for (int binCount = 0; ; ++binCount) {
                    //判斷是否有下一個元素
                    if ((e = p.next) == null) {
                        //沒有下一個元素,就將當前元素存入為下一個元素
                        p.next = newNode(hash, key, value, null);
                        //判斷當前連結串列長度是否超過了7,則將連結串列元素轉換為紅黑樹,跳出迴圈
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //判斷當前index的元素是否一樣,一樣就賦值替換,跳出
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //
            if (e != null) { // existing mapping for key
                //將老元素的value提取出
                V oldValue = e.value;
                //判斷是否覆蓋老元素的值
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                //這個方法是給LinkedHashMap留得,因為他用的也是這個put方法
                afterNodeAccess(e);
                return oldValue;
            }
        }
        //更新結構更改次數
        ++modCount;
        //判斷+1後的size是否大於閥值預設計算出的是12)
        if (++size > threshold)
            resize();
        //這個方法是給LinkedHashMap留得,因為他用的也是這個put方法
        afterNodeInsertion(evict);
        return null;
    }

下面說下resize
int threshold;
static final int MAXIMUM_CAPACITY = 1 << 30;
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;
final float loadFactor;
    
// 擴容方法,不能被重寫
     final Node<K, V>[] resize() {
           Node<K, V>[] oldTab = table;
           // 獲取當前容量
           int oldCap = (oldTab == null) ? 0 : oldTab.length;
           // 獲取擴容閥值,預設0
           int oldThr = threshold;
           // newCap 新容量 newThr 新閥值
           int newCap, newThr = 0;

           if (oldCap > 0) {
                // 判斷其容量如果大於最大值就將擴容閥值設定為Integer最大值
                if (oldCap >= MAXIMUM_CAPACITY) {
                      threshold = Integer.MAX_VALUE;
                      return oldTab;
                      // 判斷當前容量的兩倍是否小於最大容量限定,並且當前容量是否大於等於預設的16 ,滿足條件就將當前容量擴大一倍
                } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
                      newThr = oldThr << 1; // double threshold
           } else if (oldThr > 0) // initial capacity was placed in threshold
                newCap = oldThr;
           // 走到這代表是個新map首次建立
           else { // zero initial threshold signifies using defaults
                      // 初始化容量16
                newCap = DEFAULT_INITIAL_CAPACITY;
                // 初始化計算擴容閥值12
                newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
           }
           if (newThr == 0) {
                float ft = newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE);
           }
           threshold = newThr;
           @SuppressWarnings({ "rawtypes", "unchecked" })
           Node<K, V>[] newTab = new Node[newCap];
           // 賦值擴容完的node[]給teble
           table = newTab;
           // 判斷當前table是否為空,如果為null說明是新建,否則為擴容
           if (oldTab != null) {
                // 根據當前容量迴圈Node
                for (int j = 0; j < oldCap; ++j) {
                      Node<K, V> e;
                      // 先將老物件賦值給成員變數,然後判斷其是否為null
                      if ((e = oldTab[j]) != null) {
                           // 將老物件設定為null,方便垃圾回收
                           oldTab[j] = null;
                           // 如果當前元素沒有下一個元素,計算出其index並賦值給新node[]
                           if (e.next == null)
                                 newTab[e.hash & (newCap - 1)] = e;
                           // 判斷其是否為紅黑樹
                           else if (e instanceof TreeNode)
                                 ((TreeNode<K, V>) e).split(this, newTab, j, oldCap);
                           else { // preserve order 保持順序
                                 Node<K, V> loHead = null, loTail = null;
                                 Node<K, V> hiHead = null, hiTail = null;
                                 Node<K, V> next;
                                 do {
                                      next = e.next;
                                      // 根據hash oldCap計算出結果,將符合結果的元素組建成為新的連結串列lo
                                      if ((e.hash & oldCap) == 0) {
                                            if (loTail == null)
                                                 loHead = e;
                                            else
                                                 loTail.next = e;
                                            loTail = e;
                                      } else {
                                            // 將不為0的元素組建成為連結串列hi
                                            if (hiTail == null)
                                                 hiHead = e;
                                            else
                                                 hiTail.next = e;
                                            hiTail = e;
                                      }
                                 } while ((e = next) != null);
                                 // 將連結串列放到原位
                                 if (loTail != null) {
                                      loTail.next = null;
                                      newTab[j] = loHead;
                                 }
                                 if (hiTail != null) {
                                      hiTail.next = null;
                                      newTab[j + oldCap] = hiHead;
                                 }
                           }
                      }
                }
           }
           return newTab;
     }


下面說下簡單的get
public V get(Object key) {
           Node<K, V> e;
           return (e = getNode(hash(key), key)) == null ? null : e.value;
     }

final Node<K, V> getNode(int hash, Object key) {
           Node<K, V>[] tab;
           Node<K, V> first, e;
           int n;
           K k;
           //判斷當前table是否為空,並根據lenght hash算出index位置
           if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
                //直接在第一個桶中查詢看是否命中
                if (first.hash == hash && // always check first node
                           ((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 {
                           //這就是連結串列了,直接next迴圈查詢
                           if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                                 return e;
                      } while ((e = e.next) != null);
                }
           }
           return null;
     }


下一篇來說下linkedHashMap