コンテナの基礎となるソースコードの分析-ハッシュ値を計算するためのHashMap(17)

コンテナの基礎となるソースコードの分析-ハッシュ値を計算するためのHashMap(17)

  1. ハッシュ値を計算する

    • Keyオブジェクトのハッシュコードを取得します

      まず、キーオブジェクトのhashcode()メソッドを呼び出して、キーのハッシュコードの値を取得します

    • ハッシュコードに従ってハッシュ値を計算します([0、配列の長さ-1]の間隔である必要があります)

      ハッシュコードは整数です。[0、配列の長さ-1]の範囲に変換する必要があります。変換されたハッシュ値は、[0、配列の長さ-1]の範囲に均等に分散される必要があります。ハッシュの競合」

  2. 非常に単純なアルゴリズムは次のとおりです。

    • ハッシュ值=ハッシュコード/ハッシュコード

      つまり、ハッシュ値は常に1です。これは、キーと値のペアオブジェクトが配列インデックス1に格納されるため、非常に長いリンクリストが形成されることを意味します。これは、オブジェクトが格納されるたびに「ハッシュの競合」に相当し、hashMapも「リンクリスト」に縮退します。

  3. 単純で一般的に使用されるアルゴリズムは(相対剰余法)です。

    • ハッシュ値=ハッシュコード%配列の長さ

      このアルゴリズムは、ハッシュ値を[0、配列の長さ-1]の間隔で均等に分散させることができますが、このアルゴリズムは「除算」を使用するため非効率的です。JDKは後でアルゴリズムを改善しました。最初に、ビット演算を使用して余りの効果を実現できるように、配列の長さは2の整数乗でなければならないことが合意されました:hash value = hashcode&(array length-1)(&:AND演算、2進数では、2つだけが1で、それ以外の場合はすべて0です)。

  4. 最初にput()メソッドを見てみましょう。要素を追加するだけで、ハッシュ値の計算が必要になります。

    • map.put()メソッドで、Ctrl +マウスの左ボタンを使用してソースコードを入力し、次にCtrl + Altを使用してputメソッドのHashMapインターフェイスの実装クラスを選択してソースコードを入力します

      /**
           * Associates the specified value with the specified key in this map.
           * If the map previously contained a mapping for the key, the old
           * value is replaced.
           *
           * @param key key with which the specified value is to be associated
           * @param value value to be associated with the specified key
           * @return the previous value associated with <tt>key</tt>, or
           *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
           *         (A <tt>null</tt> return can also indicate that the map
           *         previously associated <tt>null</tt> with <tt>key</tt>.)
           */
          public V put(K key, V value) {
              
              
              return putVal(hash(key), key, value, false, true);
          }
      
    • 次に、hash(key)メソッドを見てみましょう。

      /**
           * Computes key.hashCode() and spreads (XORs) higher bits of hash
           * to lower.  Because the table uses power-of-two masking, sets of
           * hashes that vary only in bits above the current mask will
           * always collide. (Among known examples are sets of Float keys
           * holding consecutive whole numbers in small tables.)  So we
           * apply a transform that spreads the impact of higher bits
           * downward. There is a tradeoff between speed, utility, and
           * quality of bit-spreading. Because many common sets of hashes
           * are already reasonably distributed (so don't benefit from
           * spreading), and because we use trees to handle large sets of
           * collisions in bins, we just XOR some shifted bits in the
           * cheapest possible way to reduce systematic lossage, as well as
           * to incorporate impact of the highest bits that would otherwise
           * never be used in index calculations because of table bounds.
           */
          static final int hash(Object key) {
              
              
              int h;
              return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
          }//主要看三目运算后面的那一个是什么,(h = key.hashCode()) ^ (h >>> 16)
      //先去取key的hashCode()的值赋给h,再用h做了一个右位移16的处理
      
      
    • (h = key.hashCode())^(h >>> 16)//最初にキーのhashCode()の値を取得してhに割り当て、次にhを使用して16だけ右シフトします。 32ビット整数は16ビットを取ります(16ビットによる右シフトは左から右への16ビットです)

      ^(排他的論理和演算、同じは0、差は1)

      hashCode()の値が次のようになっているとします:456789

      対応するバイナリ:0000 0000 0100 0101 0110 0111 1000 1001

      16ビットを右にシフトします。つまり、左から右に16ビットに移動します。000000000100 0101 ^操作を実行します。

ここに画像の説明を挿入します

  • 次に、putVal()メソッドに戻ります

      /**
         * Implements Map.put and related methods
         *
         * @param hash hash for key
         * @param key the key
         * @param value the value to put
         * @param onlyIfAbsent if true, don't change existing value
         * @param evict if false, the table is in creation mode.
         * @return previous value, or null if none
         */
        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)
                //(n - 1) & hash这个与运算就是为了计算哈希值的
                //n是我们之前分析出来的是16,16-1=15
                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;
        }
    
    
  • putVal()メソッドでハッシュ値を計算する部分を分析します

 ```java
  if ((p = tab[i = (n - 1) & hash]) == null)
      //(n - 1) & hash这个与运算就是为了计算哈希值的
      //n是我们之前分析出来的是16,16-1=15
 ```

 15的二进制:0000 0000 0000 0000 0000 0000 0000 1111再去与hash做与运算

ここに画像の説明を挿入します

  1. 一般的な分析
    • hashcode()値を取得し、最初にそれをバイナリに変換して、32ビット整数にします。
    • 最初の16桁に移動し、32桁を入力します。
    • 次にXOR演算を実行すると、ハッシュになります
    • ハッシュおよびn-1バイナリAND演算
    • ハッシュ値を取得する

おすすめ

転載: blog.csdn.net/Xun_independent/article/details/114766364