jdk1.8中HashMap源碼解析

前提:學習HashMap的底層代碼以前,首先要對數據結構要個大體的瞭解。其中重點了解數組,鏈表,樹的概念和用法。數組

一.圖示分析HashMap的結構

(1).圖示爲JDK1.8以前的HashMap結構。數組+鏈表,數組中的元素爲鏈表的頭節點。若是不一樣的key對應相同的hash值,則會在頭節點後造成鏈表。
經過代碼的實現,咱們能夠分析出:若是在儲存數據時,某一個鏈表過長,則會影響查詢性能。(下面會分析put和get方法,解釋鏈表過長如何影響性能)數據結構

249993-20170725101800296-127995101.png

(2).JDK1.8中進行了優化。當鏈表長度即將超過閥值(TREEIFY_THRESHOLD),會把鏈表轉化爲紅黑樹。底層實現變爲數組+鏈表+紅黑樹
249993-20170725102326906-1051203702.png函數

二.經過代碼分析底層結構中元素的結構和原理

(1).數組和鏈表中的每一個元素都是Node<K,V>對象,它是HashMap的一個內部類而且實現了Map.Entry<K,V>。其中包含了幾個重要屬性,int hash; K key; V value; Node<K,V> next;性能

static class Node<k,v> implements Map.Entry<k,v> {
    final int hash;//經過key的hashCode方法獲取的hash值。hash值相同的key會在數組中找到同一個位置
    final K key;
    V value;
    Node<k,v> next;//指向下一個節點,儲存了下一個節點的地址
  
    Node(int hash, K key, V value, Node<k,v> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
 
    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + = + value; }
 
    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }
 
    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }
    
    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<!--?,?--> e = (Map.Entry<!--?,?-->)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

(2).當鏈表長度達到閥值,鏈表轉化爲紅黑樹。此時數組和紅樹中的元素是TreeNode<K,V>學習

static final class TreeNode<k,v> extends LinkedHashMap.Entry<k,v> {
    TreeNode<k,v> parent;  // 父節點
    TreeNode<k,v> left; //左子樹
    TreeNode<k,v> right;//右子樹
    TreeNode<k,v> prev;    // needed to unlink next upon deletion
    boolean red;    //顏色屬性
    TreeNode(int hash, K key, V val, Node<k,v> next) {
        super(hash, key, val, next);
    }
}

(3)HashMap中幾個經常使用的屬性和構造方法優化

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
    // 序列號
    private static final long serialVersionUID = 362498820763181265L;    
    // 默認的初始容量是16,經過移位運算得到結果。在計算機底層位運算速度最快
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;  
    // 最大容量 2的30次方
    static final int MAXIMUM_CAPACITY = 1 << 30; 
    // 默認的加載因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    // 鏈表轉紅黑樹的閥值
    static final int TREEIFY_THRESHOLD = 8; 
    // 擴容時紅黑樹轉鏈表的閥值
    static final int UNTREEIFY_THRESHOLD = 6;
    // 鏈表轉化爲紅黑樹對應的table的最小值
    static final int MIN_TREEIFY_CAPACITY = 64;
    // 存儲節點的數組,必定是2的冪次方
    transient Node<k,v>[] table; 
    //
    transient Set<map.entry<k,v>> entrySet;
    // 數組中元素的個數,注意這個不是數組的長度。
    transient int size;
    // 修改HashMap的次數
    transient int modCount;   
    // 閥值,當數組中的元素大於threshold時HashMap進行擴容 threshold = (數組長度)capacity * loadFactor
    int threshold;
    // 填充因子
    final float loadFactor;
    
    //無參構造函數,比較經常使用
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; 
    }
    
    //一個參數,數組容量
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    
    //兩個參數,數組容量和加載因子
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        //由於咱們的入參指定了數組大小,可是傳入的值可能不知足HashMap要求。
        //所以HashMap使用tableSizeFor保證數組大小必定是2的n次冪      
        this.threshold = tableSizeFor(initialCapacity);
    }
    /**
      此方法的目的是:保證有參構造傳入的初始化數組長度是>=cap的最小2的冪次方。
      對n不斷的無符號右移而且位或運算,能夠將n從最高位爲1開始的全部右側位數變成1,
      最後n+1即成爲大於cap的最小2的冪次方。
      第一行 n=cap-1 是爲了保證若是cap自己就是2^n,那麼結果也將是其自己
    */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

    //傳入一個HashMap實例
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
    
    //若是傳入的HashMap中有元素,遍歷它而且把其中的元素put到當前HashMap中  
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {        
            if (table == null) { //當前數組爲空           
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                        (int)ft : MAXIMUM_CAPACITY);          
                if (t > threshold)
                    threshold = tableSizeFor(t);//計算數組長度,而且保證長度是2的冪次方
            }      
            else if (s > threshold)
                resize();        
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }
    
    
}

未完待續this

相關文章
相關標籤/搜索