Analysis of the underlying source code of the container-HashMap to calculate the Hash value (17)

Analysis of the underlying source code of the container-HashMap to calculate the Hash value (17)

  1. Calculate the hash value

    • Get the hashcode of the Key object

      First call the hashcode() method of the key object to obtain the value of the hashcode of the key

    • Calculate the hash value according to the hashcode (required to be in the interval of [0, array length-1])

      Hashcode is an integer. We need to convert it into the range of [0, array length-1]. We require the converted hash values ​​to be evenly distributed in the range of [0, array length-1] to reduce "hash conflicts"

  2. An extremely simple algorithm is:

    • hash值=hashcode/hashcode

      In other words, the hash value is always 1. This means that the key-value pair object will be stored in the array index 1, thus forming a very long linked list. It is equivalent to a "hash conflict" every time an object is stored, and the hashMap also degenerates into a "linked list".

  3. A simple and commonly used algorithm is (relative remainder method)

    • hash value = hashcode% array length

      This algorithm can make the hash value evenly distributed in the interval of [0, array length-1], but this algorithm is inefficient due to the use of "division". JDK later improved the algorithm. First, it was agreed that the length of the array must be an integer power of 2, so that the use of bit operations can achieve the effect of the remainder: hash value=hashcode&(array length-1) . (&: AND operation, in a binary number, only two are 1, otherwise all are 0).

  4. Let's first look at the put() method, only adding elements will involve the calculation of the hash value

    • In the map.put() method, use Ctrl+left mouse button to enter the source code, and then use Ctrl+Alt to select the implementation class of the HashMap interface in the put method to enter the source code

      /**
           * 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);
          }
      
    • Then we look at the hash(key) method

      /**
           * 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)//First get the value of the key's hashCode() and assign it to h, and then use h to do a right shift by 16, that is, the 32-bit The integer takes 16 bits (right shift by 16 bits is 16 bits from left to right)

      ^(Exclusive OR operation, the same is 0, the difference is 1)

      Suppose the value of hashCode() is: 456789

      Corresponding binary: 0000 0000 0100 0101 0110 0111 1000 1001

      Shift 16 bits to the right, that is, go to 16 bits from left to right: 0000 0000 0100 0101 do the ^ operation

Insert picture description here

  • Then return to the putVal() method

      /**
         * 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;
        }
    
    
  • Analyze the part that calculates the hash value in the putVal() method

 ```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做与运算

Insert picture description here

  1. A general analysis :
    • Take a hashcode() value and first convert it to binary and make it into a 32-bit integer.
    • Go to the first 16 digits and fill in the 32 digits,
    • Then do XOR operation, and it will be a hash
    • hash and n-1 binary AND operation
    • Get the hash value

Guess you like

Origin blog.csdn.net/Xun_independent/article/details/114766364