高度なアーキテクチャの進化のHashMapソースコードはこのように学ぶべきです


引用:面接でよくある質問
質問:「HashMapを使ったことがありますが、私に話してもらえますか?」
「もちろん使ったことがありますが、HashMapはキーのデータput方式を素早く記憶し、getですぐに取り出すことができるストレージ構造です」と言い、「HashMapはスレッドセキュリティではありません.答え:HashTableはスレッドセキュリティで、synchronizedで実現されています.HashMapの値は非常に速いです」などと言います.この時、彼はすでにHashMapの道具を使いこなしていることを示しています.
質問:「HashMapがputやgetでどのように働いているか知っていますか?」
答え:「HashMapはkeyでHash値を算出し、このHash値をオブジェクトの参照にマッピングし、getするときにkeyのhash値を算出してからオブジェクトを見つけます」.この時はもう自信がないように見えます.
質問:「HashMapのkeyはなぜ文字列が多いのか、他のオブジェクトやカスタムオブジェクトが使えるのか.なぜ?」
これは研究したことがなくて、普通はStringを使うことに慣れます.
質問:「HashMapはスレッドセキュリティではないと言いましたが、スレッドセキュリティをどのように理解していますか.原理は何ですか.スレッドセキュリティの問題を回避する方法はいくつかあります.」
答え:「スレッドセキュリティとは、複数のスレッドがアクセスする場合、対象に対して予想外の結果をもたらすことであり、一般的にはロックをかけてこそスレッドが安全になる」HashMapの面接問題は、面接者のスレッド問題、Javaメモリモデル問題、スレッド可視と可変の問題、Hash計算問題、チェーンテーブル構造問題、バイナリの&、|、<>などの問題を考察することができる.だからHashMapは一人の技術の基礎を試すことができます.
一、データ構造
HashMapは配列+チェーンテーブル+レッドブラックツリー(JDK 1.8がレッドブラックツリー部分を増やした)で実現されており、以下に示すように
// Node                
  static class Node<K,Vimplements Map.Entry<K,V{
        final int hash; //  hash 
        final K key; //  key 
        V value; //  value 
        Node next; //  next  ,  TreeNode 

        Node(int hash, K key, V value, Node next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { }
        public final V getValue()      { }
        public final String toString() { }

        public final int hashCode() { }

        public final V setValue(V newValue) { }

        public final boolean equals(Object o) { }
    }

    public class LinkedHashMap<K,V{
          static class Entry<K,Vextends HashMap.Node<K,V{
                Entry before, after;
                Entry(int hash, K key, V value, Node next) {
                    super(hash, key, value, next);
                }    
          }
    }    

 // TreeNode   LinkedHashMap.Entry
    static final class TreeNode<K,Vextends LinkedHashMap.Entry<K,V{
        TreeNode parent;  // 
        TreeNode left; //
        TreeNode right; //
        TreeNode prev;    // 
        boolean red; //  ( 、 )
        TreeNode(int hash, K key, V val, Node next) {
            super(hash, key, val, next);
        }

        final TreeNode root() { }

        static  void moveRootToFront(Node[] tab, TreeNode root) { }

        final TreeNode find(int h, Object k, Class> kc) { }

        final void treeify(Node[] tab) { }

        final Node untreeify(HashMap map) { }

        final TreeNode putTreeVal(HashMap map, Node[] tab,int h, K k, V v) { }

        final void removeTreeNode(HashMap map, Node[] tab, boolean movable) { }

        final void split(HashMap map, Node[] tab, int index, int bit) { }

        /* ------------------------------------------------------------ */
        // Red-black tree methods, all adapted from CLR
        // 
        static  TreeNode rotateLeft(TreeNode root,TreeNode p) {}

        static  TreeNode rotateRight(TreeNode root,TreeNode p) { }

        static  TreeNode balanceInsertion(TreeNode root, TreeNode x) {}

        static  TreeNode balanceDeletion(TreeNode root,TreeNode x) {}       

        static  boolean checkInvariants(TreeNode t) {}

    }

二、メンバー属性
//   HashMap                    
    static final int DEFAULT_INITIAL_CAPACITY = 1 <4; 

   //HashMap 
    static final int MAXIMUM_CAPACITY = 1 <30;

    //HashMap  ,  HashMap    *   ,  resize() 
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    //  hash 
    static final int TREEIFY_THRESHOLD = 8;

    //   hash 
    static final int UNTREEIFY_THRESHOLD = 6;

    /*   hash  , , (  MIN_TREEIFY_CAPACITY )  hash  , ,  resize()   hashMap   */
    static final int MIN_TREEIFY_CAPACITY = 64;


    // Node
    transient Node[] table;

    //  hashMap   Node   set
    transient Set> entrySet;

    //  hashMap 
    transient int size;

    //  hashMap  (  value  )
    transient int modCount;

    //threshold  table.length * loadFactor, size   resize()
    int threshold;

    //  hashMap 
    final float loadFactor;

1、loadFactorパラメータ
メモリに余裕がある場合はloadFactorの設定を小さくすることをお勧めしますが、初期sizeの設定に注意し、適切でない場合は頻繁なresizeが挿入の効率に深刻な影響を及ぼすことに注意してください.メモリがきつい場合はloadFactor設定を大きくすることができますが、loadFactor設定が大きいとキー値がチェーンテーブルとして格納される確率が高くなり、平均的なクエリー時間が遅くなりますが、挿入には直接的な影響はありませんが、loadFactorが高くなり、
三、構造方法
//    1,           
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;
     /* tableSizeFor(initialCapacity)   initialCapacity  2 , 9,  hashMap  16*/
     //  hashMap   threshold 
        this.threshold = tableSizeFor(initialCapacity);
}
//tableSizeFor(initialCapacity)   initialCapacity  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 >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
// 2, ,  0.75
public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 3,
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

四、put方法
HashMapにput要素を入力すると、keyのhashCodeに基づいてhash値を再計算し、hash値に基づいてこの要素の配列内の位置(すなわち下付き)に値し、配列の位置に他の要素が格納されている場合、この位置の要素はチェーンテーブルの形で格納され、新しく追加された要素はチェーンヘッドに配置され、最初に追加された要素はチェーンテールに配置され、配列には、最後に挿入された要素が格納されます.配列の位置に要素がない場合は、その要素を配列の位置に直接配置します.
public V put(K key, V value) {
        return putVal(hash(key), key, value, falsetrue); 
    }

  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,          // onlyIfAbsent key value null , value , put 。
                   boolean evict)
 
{                                              //evict LinkedHashMap , 。
        Node[] tab; Node p; int n, i;                    // tab Node ,p tab Node ,n tab ,i tab 。
        if ((tab = table) == null || (n = tab.length) == 0)                    // table null tab 0 , table , resize() table。                        
            n = (tab = resize()).length;                        // ,HashMap :The table, initialized on first use, and resized as necessary。
        if ((p = tab[i = (n - 1) & hash]) == null)                               // (n - 1) & hash  tab i, p tab[i], 。 p null。
            tab[i] = newNode(hash, key, value, null);                 // p null , tab[i] , new Node , newNode tab[i]。
        else {                                              // p null , :p ;p ;p TREEIFY_THRESHOLD, 。
            Node e; K k;                               // e Node ,  k = p.key。
            if (p.hash == hash &&                             //HashMap key key hash , equals 。 p.key key , , p e。
                ((k = p.key) == key || (key != null && key.equals(k))))           // , HashMap key, , value 。
                e = p;                                    // p e, ? , , key , 。
            else if (p instanceof TreeNode)                                       // ,p , , p TreeNode.putTreeVal , e。
                e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);   // , tree key ? ,putTreeVal , hash TreeNode, null。
            else {                                                  // p , : / 。 , TreeNode Node 。
                for (int binCount = 0; ; ++binCount) {                 // , ,binCount 。
                    if ((e = p.next) == null) {                     // p.next null , , p , p.next, 。 e p。
                        p.next = newNode(hash, key, value, null);          // next, null, 。
                        if (binCount >= TREEIFY_THRESHOLD - 1// -1 for 1st     // , , 1, binCount , 1。
                            treeifyBin(tab, hash);                     // , treeifyBin , 。
                        break;                                // , , ,break 。
                    }
                    if (e.hash == hash &&                         // , , key , e, e value 。
                        ((k = e.key) == key || (key != null && key.equals(k))))   // key 。
                        break;                                // key , , break value 。
                    p = e;                                  // 21 ,e p ,p = e  。 p node 。
                }
            }
            if (e != null) { // existing mapping for key                // jdk , , key 。
                V oldValue = e.value;                           // oldValue, e value 。
                if (!onlyIfAbsent || oldValue == null)                 // ,onlyIfAbsent key , , onlyIfAbsent false oldValue null , 。
                    e.value = value;                              // , e value value。
                afterNodeAccess(e);                            // hashmap , , linkedHashMap 。
                return oldValue;                              // , oldValue。 put , , , oldValue。
            }
        }                                            
        ++modCount;                                      // , , key oldValue , return, , , modCount 。
        if (++size > threshold)                               // , oldValue , , ++size threshold 。
            resize();                                     // HashMap node threshold ,hashmap 。
        afterNodeInsertion(evict);                             // afterNodeAccess , linkedHashMap ,HashMap 。1
        return null;                                        // , ,put null。
    }

①.キー値が配列table[i]に対して空またはnullであるか否かを判断し、そうでなければresize()を実行して拡張する.②.キー値keyからhash値を挿入する配列インデックスiに計算し、table[i]=nullの場合、直接ノードを新規作成して追加し、⑥に転向し、table[i]が空でない場合、③に転向する.③. table[i]の最初の要素がkeyと同じかどうかを判断し、同じ値でvalueを直接上書きしないと④に転向し、ここで同じはhashCodeおよびequalsを指す.④. table[i]がtreeNodeであるか否か、すなわちtable[i]が赤黒ツリーであるか否かを判断し、赤黒ツリーであれば直接ツリーにキー値ペアを挿入し、そうでなければ⑤;⑤. table[i]を遍歴し、チェーンテーブルの長さが8より大きいかどうかを判断し、8より大きい場合はチェーンテーブルを赤黒ツリーに変換し、赤黒ツリーで挿入操作を実行し、そうでない場合はチェーンテーブルの挿入操作を行う.遍歴中にkeyが直接valueを上書きしていることを発見すればよい.⑥挿入に成功した後、実際に存在するキー値対数sizeが最大容量thresholdを超えているか否かを判断し、超えていれば拡張する.
五、resize方法
// Initializes or doubles table size,        table  
final Node[] resize() {  
        Node[] 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)                      oldCap >= DEFAULT_INITIAL_CAPACITY)  
                newThr = oldThr <1; // double threshold   
       }  
      // oldCap=0 ,oldThr>0,threshold( resize )  
       else if (oldThr > 0)   
           newCap = oldThr; // = ( )  
       else {     // oldCap=0 ,oldThr=0,   
         newCap = DEFAULT_INITIAL_CAPACITY;  
         newThr=(int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);  
        }  
        if (newThr== 0) { // 0,    
           float ft = (float)newCap * loadFactor;  
           newThr = (newCap float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);  
        }  
        threshold = newThr; //   
        @SuppressWarnings({"rawtypes","unchecked"})  
            Node[] newTab = (Node[])new Node[newCap]; //   
        table = newTab;   
         // table ,bucket copy bucket,   
        if (oldTab != null) { //  copy, !!!  
            for (int j = 0; j                Node e;  
               if ((e = oldTab[j]) != null) { //   
                  oldTab[j] = null// null, GC  
                  // , ,   
                  if (e.next == null)   
                //1.6 indexFor, key;tableSizeFor   
                    newTab[e.hash &(newCap - 1)]= e; //hash&(length-1)  
                  else if (e instanceof TreeNode) //    
                     ((TreeNode)e).split(this, newTab, j, oldCap);  
                  else { // ,preserve order   
                        // , bucket bucket  
                        Node loHead = null,loTail = null;//lo bucket   
                        Node hiHead = null, hiTail = null;//hi bucket   
                        Node 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) { // bucket ( node)  
                            loTail.next = null// null  
                            newTab[j] = loHead;// (j)   
                        }  
                        if (hiTail != null) {  //  j+oldCap  
                            hiTail.next = null;  
                            newTab[j + oldCap] = hiHead;//j+oldCap   
                        }  
                    }  
                }  
            }  
        }  
        return newTab;  
    }

六、treeifyBin方法
//       
    final void treeifyBin(Node[] tab, int hash) {
        /*int n, index; Node e;
        //  hash , ,
        if (tab == null || (n = tab.length)             resize();
        //  node
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode hd = null, tl = null;
            //  ,
            do {
                // 
                TreeNode p = replacementTreeNode(e, null);
                // 
                if (tl == null)
                    // hd
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            // 
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }*/

    }
  

詳細については、微信公衆番号:it_haha