目录


  HashMap是一个看似简单实而复杂的类,也是在面试中很容易问道的问题,哈哈哈,不怕。。。

  众所周知,HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干

  HashMap数组每一个元素的初始值都是Null

  HashMap图文源码解析

  对于HashMap,我们最常使用的是两个方法:Get  Put

Put方法的原理

  调用Put方法的时候发生了什么呢?

  比如调用 hashMap.put("apple", 0) ,插入一个Key为“apple"的元素。这时候我们需要利用一个哈希函数来确定Entry的插入位置(index):

  index =  Hash(“apple”)

  假定最后计算出的index是2,那么结果如下:

    HashMap图文源码解析

  但是,因为HashMap的长度是有限的,当插入的Entry越来越多时,再完美的Hash函数也难免会出现index冲突的情况。比如下面这样:

  HashMap图文源码解析

            HashMap图文源码解析

  这时候该怎么办呢?我们可以利用链表来解决。

  HashMap数组的每一个元素不止是一个Entry对象,也是一个链表的头节点。每一个Entry对象通过Next指针指向它的下一个Entry节点。当新来的Entry映射到冲突的数组位置时,只需要插入到对应的链表即可:

  HashMap图文源码解析

  需要注意的是,新来的Entry节点插入链表时,使用的是“头插法”。至于为什么不插入链表尾部,后面会有解释。

Get方法的原理

  使用Get方法根据Key来查找Value的时候,发生了什么呢?

  首先会把输入的Key做一次Hash映射,得到对应的index:

    index =  Hash(“apple”)

  由于刚才所说的Hash冲突,同一个位置有可能匹配到多个Entry,这时候就需要顺着对应链表的头节点,一个一个向下来查找。假设我们要查找的Key是“apple”:

  待查找Key:

  HashMap图文源码解析

  第一步:我们查看的是头节点Entry6,Entry6的Key是banana,显然不是我们要找的结果。

  第二步:我们查看的是Next节点Entry1,Entry1的Key是apple,正是我们要找的结果。

  之所以把Entry6放在头节点,是因为HashMap的发明者认为,后插入的Entry被查找的可能性更大

高并发下的HashMap(jdk8以下版本)

  在分析高并发场景之前,需要搞清楚【ReHash

  ReHash是HashMap在扩容时候的一个步骤

  HashMap的容量是有限的。当经过多次元素插入,使得HashMap达到一定饱和度时,Key映射位置发生冲突的几率会逐渐提高

  这时候,HashMap需要扩展它的长度,也就是进行Resize

  HashMap图文源码解析

  影响发生Resize的因素有两个:

  1.Capacity

  HashMap的当前长度。上一期曾经说过,HashMap的长度是2的幂。

  2.LoadFactor

  HashMap负载因子,默认值为0.75f。

 

  衡量HashMap是否进行Resize的条件如下:

    HashMap.Size   >=  Capacity * LoadFactor

  HashMap的Resize不是简单地把长度扩大,而是经过下面两步:

1.扩容

创建一个新的Entry空数组,长度是原数组的2倍。

2.ReHash

遍历原Entry数组,把所有的Entry重新Hash到新数组。为什么要重新Hash呢?因为长度扩大以后,Hash的规则也随之改变。

 

让我们回顾一下Hash公式:

index =  HashCode(Key) &  (Length - 1)

当原数组长度为8时,Hash运算是和111B做与运算;新数组长度为16,Hash运算是和1111B做与运算。Hash结果显然不同。

Resize前的HashMap:

HashMap图文源码解析

  Resize后的HashMap:

 HashMap图文源码解析

 HashMap图文源码解析

  ReHash的Java代码如下:

/**
 * Transfers all entries from current table to newTable.
 */
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}
View Code

相关文章:

  • 2021-06-29
猜你喜欢
  • 2021-08-05
相关资源
相似解决方案