1. 程式人生 > >JDK8 原始碼解析 ---HashMap

JDK8 原始碼解析 ---HashMap

##

Stack過時的類,使用Deque重新實現。

HashMap原始碼解析:

HashMap的定義:

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

繼承抽象AbstractMap,實現了Map。

HashMap中重要常量:

//預設容量

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

//最大容量

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;

//儲存方式由連結串列轉成紅黑樹的容量的最小閾值

static final int MIN_TREEIFY_CAPACITY = 64;

//HashMap中儲存的鍵值對的數量

transient int size;

//擴容閾值,當size>=threshold時,就會擴容

int threshold;

//HashMap的載入因子

final float loadFactor;

需要指出的是這裡loadFactor載入因子在初始化後就不能變更。載入因子也可以叫做擴充因子----畢竟只是拿來判斷是否擴容的嘛(#^.^#)。

初始化HashMap

Map<K,V> map = new HashMap<K,V>();

或者 Map<K,V> map = new HashMap<K,V>(31)

實際呼叫的程式碼

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

this(initialCapacity,DEAULT_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;
    this.threshold = tableSizeFor(initialCapacity);
}

在初始化中會判斷,初始化引數是否小於0,丟擲IllegalArgumentException();如果HashMap的最大容量MAXIMUM_CAPACITY(也就是2的32次方 ,為什麼是2的32次方呢? ),在確定threhold擴容閾值。

細心的同學可能注意到HashMap中所有常量的定義都是int型,Java中int型是32位的。

接下來就是對tableSizeFor的解釋:

/**
 * Returns a power of two size for the given target capacity.
 */
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;
}

原始碼中註釋的寫的是獲取與cap最相近的2的冪。

例如 32 會產生32 而33就會產生64,這個式子很神奇,為什麼正確,我也不太明瞭o(╥﹏╥)o。

Hash()方法

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

^是異或操作 1^1 =0 ;1^0 = 1; 0^1 =1 ;0^0=0; 多位的異或,比如3^2 = 1 (11^10 = 01);

這裡需要解釋的是,如果(h = key.hashCode())^(h>>>16) 是取h的低16位與高16為進行異或作為低16位與h的高16位進行拼接,得到最後的hash值。 據說能夠提高hash的分散程度。嚶嚶嚶。

還有提到一點就是HashSet內部是使用HashMap實現的;這個在解析HashSet的時候會詳細提到;

/**
    HashSet的Add的方法
*/
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

HashSet核心方法

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;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        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) {
                    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;
            }
        }
        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;
}

我們慢慢來分析。首先看入參:

  • hash:表示key的hash值

  • key:待儲存的key值

  • value:待儲存的value值,從這個方法可以知道,HashMap底層儲存的是key-value的鍵值對,不只是儲存了value

  • onlyIfAbsent:這個引數表示,是否需要替換相同的value值,如果為true,表示不替換已經存在的value

  • evict:如果為false,表示陣列是新增模式

我們看到put時所傳入的引數put(hash(key), key, value, false, true),可以得到相應的含義。

作者:端木軒

來源:簡書

簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。

HashMap中的資料結構

在繼續下一步分析之前,我們首先需要看一下HashMap底層的資料結構。

HashMap的資料結構

我們可以看到,HashMap底層是陣列加單向連結串列或紅黑樹實現的(這是JDK 1.8裡面的內容,之前的版本純粹是陣列加單向連結串列實現)。

回到最騷氣的putVal()

Node<K,V>[] tab; Node<K,V> p; int n, i;

Node<K,V>[] tab 用於引用table也就是hash表;

Node<K,V> p 用於指向需要指向紅黑樹或者連結串列;

int n 用於儲存當前table的長度;

int i 用於儲存當前訪問的table索引;

####

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);

如果當前訪問的桶為空,初始化一個新的節點

Node<K,V> e; K k;
if (p.hash == hash &&
    ((k = p.key) == key || (key != null && key.equals(k))))
    e = p;

當前的hash值等於。。。

else if (p instanceof TreeNode)
    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

當前訪問的桶是紅黑樹,將該值放入紅黑樹中;

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;
    }
    if (e.hash == hash &&
        ((k = e.key) == key || (key != null && key.equals(k))))
        break;
    p = e;
}

如果桶是空的,將節點新增到連結串列後面,如果當前連結串列長度大於TREEIFY_THRESHOLD將當前連結串列轉化為紅黑樹。

if (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
        e.value = value;
    afterNodeAccess(e);
    return oldValue;
}

如果當前key不為空,並且開啟了替換模式,將值直接替掉;

++modCount;
if (++size > threshold)
    resize();

當前值大小大於閾值,將當前大小重新定製;到此,最核心的putVal()就淺顯的將完成了,雖然還有很多的疑惑為解決;

// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

在程式碼中這三個方法體時為空的,用於LinkedHashMap的操作;

#####

參考內容: