HashMap原始碼(JDK1.8)-手動註釋

CodeJames發表於2021-02-04

HashMap簡介

HashMap是一種K-V對映的一種資料結構,通過K(key)值能實現在O(1)的時間複雜度下找到對應的V(value)。JDK1.8之前,HashMap的底層資料結構是陣列+連結串列,陣列中的每個元素稱為一個Entry,包含(hash,key,value,next)這四個元素,其中連結串列是用來解決碰撞(衝突)的,如果hash值相同即對應於陣列中同一一個下標,此時會利用連結串列將元素插入連結串列的尾部,(JDK1.8是頭插法)。在JDK1.8及之後,底層的資料結構是:陣列+(連結串列,紅黑樹),引入紅黑樹是為了避免連結串列過長,影響元素值的查詢,因此當整體的陣列大小大於64時,並且連結串列的長度大於或等於8時,會把連結串列轉化成紅黑樹。在HashMap這一資料結構中,常見的方法有get、put等,在查詢或者插入元素時都會建立臨時陣列和指標。常見的Map有:HashMap、TreeMap、LinkedHashMap、HashTable、concurrentHashMap等。掌握HashMap對於面試時很有幫助的,這是面試常問的知識點。

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;
    } 
    //getKey
    public final K getKey()  {return key;}
    public final V getValue() {return value;}
    public final String toString() {return key + "=" + value;}
    //重點,面試常備問道,求hashCode的值是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;
    }
    
    
    //需要重寫equals,當key,value同時相等時,才相等
    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;
    }
    
    //hash是key值的hashcode高低16位異或,從這裡可以知道jdk1.8hashmap的key是可以為null
    //若為null,取0,hashtable中的key是不能為null的
    static final int hash(Object key){
        int h;
        return (key == null)?0:(h = key.hashCode()^(h>>>16));
    }
    
    //給出一個初始容量,會根據給定的初始容量,給出最近的2的多少平方
    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 >= MAXIMU_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
    
    //同上,另一種實現方法,得到最近的2的多少的平方
    static final int tableSizeFor(int cap){
        int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
        return (n < 0) ? 1 : (n >= MAXIMU_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
    
    //其中numberOfLeadingZeros方法,採用了從大到小進行判斷
    public static int numberOfLeadingZeros(int i){
        if(i <= 0){
            return i == 0 ? 32 : 0;
        }
        int n = 31;
        if(i >= 1 << 16) {n -= 16; i >>>= 16;}
        if(i >= 1 << 8) {n -= 8; i >>>= 8;}
        if(i >= 1 << 4) {n -= 4; i >>>= 4;}
        if(i >= 1 << 2) {n -= 2; i>>>= 2;}
        return n - (i >>> 1);
    }
    
    //初始化HashMap
    public HashMap(int initialCapacity, float loadFactor){
        if(initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity:" + initialCapacity);
        if(initialCapacity > MAXIMUM_CAPACITY){
            initalCapacity = MAXIMUM_CAPACITY;
        }
        if(loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
        this.loadFactor = loadFactor;
        this.threshlod = tableSizeFor(initialCapacity);
    }
    //獲取特定key的value值
    public V get(Object key){
        Node<K,V> e;
        return (e = getNode(hash(key),key)) == null ? null : e.value;
    }
    //通過hash、key獲取Node
    final Node<K,V> getNode(int hash, Object 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 && //總是先檢查第一個結點
              ((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);//進行迴圈,一直比對hash、key是否相等
        }
        return null;
    }
    //利用getNode進行判斷
    public boolean containsKey(Object key){return getNode(hash(key),key) != null;}
    
    //put--(key,value)
    public V put(K key, V value){
        return putVal(hash(key),key,value,false,true);
    }
    //重點來看看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)//如果此時table資料為空,進行擴容
            n = (tab = resize()).length;
        if((p = tab[i = (n - 1)&hash]) == null)  //若找到下標,此時沒有值,即為null,則建立結點
            tab[i] = newNode(hash,key,value,null);
        else{//否則將將進行遍歷連結串列
            Node<K,V> e; K k;
            //先檢查頭結點,如果hash,key相等,則已經插入了該結點
            if(p.hash == hash &&
              ((k = e.key) == key) || (key != null && key.equals(k)))
                e = p;
            //判斷插入的結點p是否是樹結點
            else if(p isinstanceof 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);
                        //如果binCount>=7時,連結串列樹化為紅黑樹
                        if(binCount >= TREEIFY_THRESHOLD - 1)
                            treeifyBin(tab,hash);
                        break;
                    }//找到相等的結點,直接break
                    if(e.hash == hash && ((k = e.key) == key ||(key != null && key.equals(k))))
                        break;
                    //移動連結串列指標,指向下一個結點
                    p = e;
                }
            }
            if(e != null){//存在對映關係,但是value為空
                V oldValue = e.value;
                if(!onlyIfAbsent || oldValue == value)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if(++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    
    final Node<K,V>[] resize(){
        //定義oldTab,oldThr
        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){
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }//容量放大兩倍,閾值也放大兩倍
            else if((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                   oldCap >= DEFALUT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // 閾值放大兩倍
        }
        //此時,oldCap等於零,但是閾值oldThr大於零,直接用oldThr進行替換
        else if(oldThr > 0)
            newCap = oldThr;//用thre替換初始容量
        else {//此時,oldCap和oldThr都為零,進行初始值替換,表明第一次擴容
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACOR * DEFAULT_INITIAL)
        }
        //初始化閾值newThr,初始化threshold
        if(newThr == 0){
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY)?(int)ft : Integer.MAX_VALUE;
        }
        threshold = newThr;
        //建立Node型陣列,對table進行賦值
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        //將oldTab中元素賦值給newTab
        if(oldTab != null){
            for(int j = 0;j < oldCap; ++j){
                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 TreeNode)//判斷是樹節點
                        ((TreeNode<K,V>)e).split(this,newTab, j,oldCap);
                    else{ //rehash,高位等於索引+oldCap,即:j + oldCap;
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do{
                            next = e.next;
                            if((e.hash & oldCap) == 0){ //表明是原來的位置,看這個地方是否有頭結點,若無直接賦值,否則從尾部插入
                                if(loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }//表明需要從新hash到新的位置,同理如上
                            else{
                                if(hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        }while((e = next) != null);
                        if(loTail != null){ //先建立一個連結串列,之後將這個連結串列接到j索引處
                            loTail.next = null;
                            newTab[j] = loHead;
                        } // 同理只是更改了索引位置:j + hiHead
                        if(hiTail != null){
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
    
    public V remove(Object key){
        Node<K,V> e;
        return (e = removeNode(hash(key),key,null,false,true)) == null ? null : e.value;
    }
    final Node<K,V> removeNode(int hash, Object key,Object value,
                              boolean matchValue,boolean movable){
        //定義臨時變數
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        if((tab = table) != null && (n = tab.length) > 0 &&
          (p = tab[index = (n - 1) & hash]) != null){
            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;
            //往下遍歷
            else if((e = p.next) != null){
                if(p instanceof TreeNode)//是樹形節點
                    node = ((TreeNode<K,V>) p).getTreeNode(hash,key);
                else {
                    do {//進行迴圈遍歷,找到即跳出迴圈
                        if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
                            node = e;
                            break;
                        }
                        p = e;
                    } while((e = e.next) != null);
                }
            }
            if(node != null && (!matchValue || (v = node.value) == value ||
                               (value != null && value.equals(v)))){
                if(node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this,tab,movale);
                //若判斷是頭結點,直接去掉頭結點,接在後面
                else if(node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;//在連結串列中間,跳過該節點
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }
}

相關文章