HashMap 基礎原理以及原始碼解讀

以技入道發表於2017-08-15

  第一次寫部落格,有理解或者表述不對的地方望博友指出,一起交流,一起進步。

  基礎的資料結構,大家都不陌生, 基於 陣列、連結串列之類 大家一定不陌生,今天我們們聊一聊他們的具體儲存原理和效率。

陣列 : 佔用空間大,故空間複雜度大 ,但是陣列的二分法查詢時間複雜度小 ,查詢容易,增加刪除困難
連結串列 : 連結串列儲存空間離散, 佔用記憶體比較寬鬆,故空間複雜度很小,但時間複雜度較大 , 所以查詢困難 ,增加刪除容易
  以上兩種資料結構效率均有所不如人意,雜湊表就滿足了查詢容易,和增刪容易

關於雜湊表,最常用的實現就是拉鍊法-->也就是連結串列的陣列

雜湊表本身由陣列+連結串列 組成,一個長度為16的陣列中,每個元素儲存的是一個連結串列的頭結點。那麼這些元素是按照什麼樣的規則儲存到陣列中呢。一般情況是通過hash(key)%len獲得,也就是元素的key的雜湊值對陣列長度取模得到。

HashMap是一個線性的陣列實現的,所以可以理解為其儲存資料的容器就是一個線性陣列。這可能讓我們很不解,一個線性的陣列怎麼實現按鍵值對來存取資料呢?這裡HashMap有做一些處理。

看下HashMap的原始碼 jdk1.8可以發現  HashMap 的 put方法

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;//n 陣列長度 tab為陣列
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)// 如果雜湊值對應的值不存在,在陣列對應位置新建陣列 確定陣列index
        tab[i] = newNode(hash, key, value, null);
    else {//否則確定陣列index,對應位置新建連結串列
        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)//如果size超過threshold,則擴充table大小。再雜湊
        resize();
    afterNodeInsertion(evict);
    return null;
}
HashMap 的get方法原始碼部分(查詢)
final Node<K,V> getNode(int hash, Object key) {//檢查對應的雜湊值是否一樣,確定陣列index  再根據key找到對應的連結串列的位置 返回
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    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 {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}
參考答案 http://blog.csdn.net/vking_wang/article/details/14166593


相關文章