元素在数组中的位置由key.hashCode()的值决定,如果两个key的哈希值相等,即发生了哈希碰撞,用链地址法解决 Hash 碰撞

当然这张图中没有体现出来的有两点:

  1. 为了提升整个HashMap的读取效率,当 HashMap 中的元素个数超过 数组大小*负载因子 时 扩容,以减小哈希碰撞
  2. 在 Java 8 中如果桶数组的同一个位置上的 链表数量超过一个定值,则整个链表有一定概率会转为一棵红黑树。

题外,解决 Hash 碰撞的其它方法:

1、ReHash :多个次选哈希函数。在发生冲突时,再用第二个,第三个...哈希函数算出哈希值,直到算出的哈希值不同为止。虽然不易发生聚集,但增加了计算时间。

2、开放地址法(再散列法):Hi=(H(key)+di) MOD m i=1,2,…,k (k<=m-1)

  • 如果di值可能为1,2,3,…m-1,称线性探测再散列。如果di取1,则每次冲突之后,向后移动1个位置。
  • 如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,…kk,-kk(k<=m/2),称二次探测再散列。
  • 如果di取值可能为伪随机数列。称伪随机探测再散列。

 

 

 

怎么样才是一个好的hash函数呢?

  • 计算出来的哈希值足够散列,能够有效减少哈希碰撞
  • 本身能够快速计算得出,因为HashMap每次调用get和put的时候都会调用hash方法

怎么计算初始桶容量的大小?怎么实现大小为大于 cap 的最小 2 的整数幂次方?

  找到大于指定cap的最小的2的整数幂

  >>> 无条件右移符号,|= 代表布尔值或。

  假设 n 为 0001 xxxx 

  0001 xxxx 进行 n >>> 1 为 0000 1xxx ,0001 xxxx |= 0000 1xxx 为  0001 1xxx

  0001 1xxx 进行 n >>> 2 为 0000 011x ,0001 1xxx |= 0000 011x 为 0001 111x

  0001 111x 进行 n >>> 4 为 0000 0001,0001 111x |= 0000 0001 为 0001 1111

  .......

  最后 n 会变为 0001 1111 ,低位全为 1

  return 的时候再 +1,变为 0010 0000 ,即大于指定cap的最小的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;
}

  

 

 

如果没有指定 cap 大小,bucket 的默认大小是多少?扩容的阈值负载因子是多少?

  默认大小是 16,发生扩容的默认负载因子是 0.75。也就是说,默认情况下,那么当HashMap中元素个数超过 16*0.75=12 的时候,就把数组的大小扩展为 2*16 = 32,即扩大一倍。

HashMap 什么时候开辟 bucket 数组占用内存?

在HashMap第一次put的时候,无论Java 8还是Java 7都是这样实现的。这里我们可以看到两个版本的实现中,桶数组的大小都是2的正整数幂。

HashMap 内部的 bucket 数组长度为什么要为2的整数次幂?

  第一

  通过哈希函数得到哈希计算结果后,把这个结果放在桶的哪个位置,要除以桶的大小取余

这个算法实际就是取模,hash % length,计算机中 直接求余效率远不如位移运算,源码中做了优化hash & (length - 1 )

hash % length == hash & ( length - 1 ) 的 前提是length是2的n次方

例如 14 除以 8 取余结果为 6 , 相当于  1110 &  0111 (7)= 0110

例如 长度是 0000 0000 1000 0000  哈希结果对它 -1 取余就是  0010 1010 0101 0001 & 0000 0000 0111 1111 = 0000 0000 0101 0001

相当于舍弃了哈希结果,自长度 2的 n 次以上的高位信息,保留了低位信息

第二

如果在put数据之后超过了threshold的值,则需要扩容,扩容意味着桶数组大小变化。HashMap寻址是通过对列表大小求余来计算的,现在列表大小发生了变化,势必会导致部分key的位置也发生了变化,HashMap是如何设计的呢?

这里就涉及到桶数组长度为2的正整数幂的第二个优势了:当桶数组长度为2的正整数幂时,如果桶发生扩容(长度翻倍),则桶中的元素大概只有一半需要切换到新的桶中,另一半留在原先的桶中就可以,并且这个概率可以看做是均等的。