JDK1.8HashMap源碼分析

HashMap和Hashtable的主要區別是:
1. Hashtable是線程安全,而HashMap則非線程安全,Hashtable的實現方法裏面大部分都添加了synchronized關鍵字來確保線程同步,所以相對而言HashMap性能會高一些,在多線程環境下若使用HashMap須要使用Collections.synchronizedMap()方法來獲取一個線程安全的集合。
java

2. HashMap的鍵和值均可覺得null,而Hashtable的鍵值都不能爲null。node

3. HashMap的初始容量爲16,Hashtable初始容量爲11,二者的填充因子默認都是0.75。HashMap擴展容量是當前容量翻倍即:capacity*2,Hashtable擴展容量是容量翻倍+1即:capacity*2+1(關於擴容和填充因子後面會講)算法

4. 二者的哈希算法不一樣,HashMap是先對key(鍵)求hashCode碼,而後再把這個碼值得高位和低位作異或運算,源碼以下:數組

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


而後把hash(key)返回的哈希值與HashMap的初始容量(也叫初始數組的長度)減一作&(與運算)就能夠計算出此鍵值對應該保存到數組的那個位置上(hash&(n-1))。安全

而Hashtable計算位置的方式以下:多線程

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;


直接計算key的哈希碼,而後與2的31次方作&(與運算),而後對數組長度取餘數計算位置。app

 

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;

 

接下來是HashMap的put和remove源碼分析性能

HashMap結構就是Node數組,Node源碼:this

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        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;
        }
    }

put源碼分析:

final V putVal ( int hash, K key, V value,boolean onlyIfAbsent,
        boolean evict){
            HashMap.Node<K, V>[] tab;
            HashMap.Node<K, V> p;
            int n, i;
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;//第一次調用的時候,數組長度爲零,調用resize(),此時數組長度變爲16
            if ((p = tab[i = (n - 1) & hash]) == null)//根據傳入的hash值,算出Node所在的數組標,拿出Node,若是爲空表示當前數組尚未鏈表,建立新的節點放在數組這個位置
                tab[i] = newNode(hash, key, value, null);
            else {//若取出的節點p不爲空
                HashMap.Node<K, V> e;
                K k;
                if (p.hash == hash &&
                        ((k = p.key) == key || (key != null && key.equals(k))))//只有當取出的節點hash值與Key都與傳參同樣時才認爲兩個Node爲同一個
                    e = p;// e指向原有的Node
                else if (p instanceof HashMap.TreeNode)//若是找到的當前節點不是key對應的Node,且爲TreeNode,執行treeNode的put邏輯
                    e = ((HashMap.TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
                else {
                    for (int binCount = 0; ; ++binCount) {// binCount 用來記錄當前鏈表長度
                        if ((e = p.next) == null) {//若是當前Node 不是咱們要找的,遍歷這個鏈表數據
                            p.next = newNode(hash, key, value, null);//若是節點的下一個節點爲空,建立新的節點,放在現有的節點後面
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st,若是鏈表長度 >= 8 ,則將鏈表轉換爲紅黑樹
                                treeifyBin(tab, hash);//轉換爲紅黑樹
                            break;
                        }
                        if (e.hash == hash &&
                                ((k = e.key) == key || (key != null && key.equals(k))))//若是當前節點的下一個節點就是咱們要找的元素,直接跳出for循環,e是最終要找的Node
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key,若是e不爲空,表示找到了對應的節點
                    V oldValue = e.value;//oldValue 用戶存儲老的值
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;//將新值覆蓋原有的值
                    afterNodeAccess(e);
                    return oldValue; // 找到對應的節點之後,返回值爲舊value
                }
            }
            //如下代碼爲只有當e爲空,也就是沒有找到相匹配的key值得地方,這是就建立了一個新的Node,添加到某個鏈表後面
            ++modCount;// modCount 用於記錄map 被修改了多少次
            if (++size > threshold)// size 表示當前map 有多少個 Node,添加一個元素Node
                resize();//當添加了一個Node之後 size 若是大於 承載量 則進行擴容
            afterNodeInsertion(evict);
            return null;//若是沒有找到你對應的節點,執行新增操做,返回null


        }

remove源碼分析:
 

final HashMap.Node<K, V> removeNode ( int hash, Object key, Object value,
        boolean matchValue, boolean movable){
            HashMap.Node<K, V>[] tab;
            HashMap.Node<K, V> p;
            int n, index;
            if ((tab = table) != null && (n = tab.length) > 0 &&
                    (p = tab[index = (n - 1) & hash]) != null) {//根據hash值定位到要移除的元素位置
                HashMap.Node<K, V> node = null, e;
                K k;
                V v;
                if (p.hash == hash &&
                        ((k = p.key) == key || (key != null && key.equals(k))))
                    node = p;//若是找到了對應的元素,讓臨時變量node指針指向被刪除元素
                else if ((e = p.next) != null) {//若是當前節點沒有找到,則向下遍歷鏈表
                    if (p instanceof HashMap.TreeNode)//若是是TreeNode ,將節點強制轉換爲TreeNode
                        node = ((HashMap.TreeNode<K, V>) p).getTreeNode(hash, key);
                    else {
                        do {
                            if (e.hash == hash &&
                                    ((k = e.key) == key ||
                                            (key != null && key.equals(k)))) {//遍歷整個鏈表,若是找到要刪除的節點就將node指針指向該節點
                                node = e;
                                break;
                            }
                            p = e;
                        } while ((e = e.next) != null);
                    }
                }
                //若是node不爲空,則表示找到了要刪除的節點
                if (node != null && (!matchValue || (v = node.value) == value ||
                        (value != null && value.equals(v)))) {//該節點值與要刪的節點value一致,執行刪除動做
                    if (node instanceof HashMap.TreeNode)//若是當前節點是treeNode,則執行treeNode的刪除邏輯
                        ((HashMap.TreeNode<K, V>) node).removeTreeNode(this, tab, movable);
                    else if (node == p)//若是當前節點就是要刪除的節點,則把節點的下一個節點放在數組位置,相似於把頭給掐斷
                        tab[index] = node.next;
                    else//若是要刪除節點不在頭部的話,這種狀況下,p始終爲要刪除節點的前一個節點
                        p.next = node.next;//將p節點的next指針指向node的下一個節點,即將node節點刪除
                    ++modCount;//統計map被修改的次數
                    --size;//map的 Node 個數減一
                    afterNodeRemoval(node);
                    return node;//返回被刪除的Node節點
                }
            }
            return null;//若是沒找到被刪除節點 ,返回null
        }

擴容源碼分析:

final HashMap.Node<K,V>[] resize() {
            HashMap.Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;//舊的容量,即數組長度
            int oldThr = threshold;//舊的承載量
            int newCap, newThr = 0;//聲明兩個變量存儲新的容量和裝載量
            if (oldCap > 0) {
                if (oldCap >= MAXIMUM_CAPACITY) {//若是舊的容量大於容量最大值,不進行擴容,將Integer最大值給裝載容量
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                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;//若是容量爲零(數組爲空)但承載量不爲0,表示map中之前有過元素,將承載量賦值給容量
            else {               // zero initial threshold signifies using defaults
                newCap = DEFAULT_INITIAL_CAPACITY;// 第一次調用方法的時候初始化容量,16
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//初始化承載量,容量*0.75(承載因子)
            }

            if (newThr == 0) {//若是承載量爲零,則從新賦值
                float ft = (float)newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                        (int)ft : Integer.MAX_VALUE);
            }
            threshold = newThr;//覆蓋原有承載量
            @SuppressWarnings({"rawtypes","unchecked"})
            HashMap.Node<K,V>[] newTab = (HashMap.Node<K,V>[])new HashMap.Node[newCap];
            table = newTab;//將原有節點數組指向根據新的容量建立新的節點數組
            if (oldTab != null) {//若是原有數組不爲空,則將數組上的每個鏈表都拆分爲兩份,一份存儲在原有數組位置,一部分存儲在新擴容的數組位置,讓節點元素保持均勻分佈
                for (int j = 0; j < oldCap; ++j) {
                    HashMap.Node<K,V> e;
                    if ((e = oldTab[j]) != null) {
                        oldTab[j] = null;
                        if (e.next == null)
                            newTab[e.hash & (newCap - 1)] = e;
                        else if (e instanceof HashMap.TreeNode)
                            ((HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                        else { // preserve order
                            HashMap.Node<K,V> loHead = null, loTail = null;
                            HashMap.Node<K,V> hiHead = null, hiTail = null;
                            HashMap.Node<K,V> next;
                            do {
                                next = e.next;
                                if ((e.hash & oldCap) == 0) {
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                else {
                                    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;
        }

其中,爲何擴容耗時就很明顯了,擴容的流程是將原有數組擴大一倍,將數組上的每個鏈表一分爲二,均勻地分佈在數組上,而java8有引進了紅黑樹,當鏈表長度大於8時,將鏈表轉換爲紅黑樹,這樣大大加快了get(key)的速度。

相關文章
相關標籤/搜索