容器-HashMap计算Hash值底层源码分析(十七)

容器-HashMap计算Hash值底层源码分析(十七)

  1. 计算Hash值

    • 获得Key对象的hashcode

      首先调用key对象的hashcode()的方法,获得key的hashcode的值

    • 根据hashcode计算出hash值(要求在[0,数组长度-1]区间)

      hashcode是一个整数,我们需要转化成[0,数组长度-1]的范围,我们要求转换后的hash值尽量均匀的分布在[0,数组长度-1]这个区间,减少“hash冲突”

  2. 一种极端简单的算法是:

    • hash值=hashcode/hashcode

      也就是说,hash值总是1.意味着,键值对对象都会存储到数组索引1位置,这样就形成了一个非常长的链表。相当于每存储一个对象都会发生“hash冲突”,hashMap也退化成了一个"链表"。

  3. 一种简单和常用的算法是(相对取余法)

    • hash值=hashcode%数组长度

      这种算法可以让hash值均匀的分布在[0,数组长度-1]这个区间,但是,这种算法由于使用了“除法”,效率低下。JDK后来改进了算法,首先约定数组长度必须为2的整数幂,这样采用位运算即可实现取余的效果:hash值=hashcode&(数组长度-1)。(&:与运算,二进制的数中,两个为才为1,否则全为0).

  4. 我们先去看put()的方法,只有添加元素,才会涉及到hash值的运算

    • 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)//先去取key的hashCode()的值赋给h,再用h做了一个右位移16的处理,就是把32位的整数取了16位(右位移16位也就是从左到右去16位)

      ^(异或运算,相同为0,相异为1)

      假设hashCode()的值是:456789

      对应的二进制:0000 0000 0100 0101 0110 0111 1000 1001

      和右位移16位也就是从左到右去16位:0000 0000 0100 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位,
    • 然后做异或运算,算出来是一个hash
    • hash和n-1的二进制做与运算
    • 得出hash值

猜你喜欢

转载自blog.csdn.net/Xun_independent/article/details/114766364
今日推荐