一、简述

1、HashMap是JDK中较为常用的一种数据类型,是“数组+链表”的数据结构,也是当今世界检索最快的哈希算法;
2、它是基于哈希表的Map接口的实现,并允许使用null值 和 null键(即key 与 value 可为空);
3、它是非线程安全的,也就是说多线程情况下可能会出现问题,此处与HashTable不同;
4、HashMap 是一个用于存储Key-Value 键值对的集合,每一个键值对也叫做Entry,这些HashMap数组每一个元素的初始值都是null,长度为16(下面有介绍);

二、最常用的方法(Put && Get)

1、Put 方法原理

当我们调用

hashMap.put(“flower”, niufen);

当我们插入一个Key为“flower”的元素。这时候我们需要利用一个哈希函数来确定Entry的插入位置(index):

index = Hash(“flower”);

假定最后计算的结果index为2,结果如下

hash算法数据结构 mysql hashmap数据结构及原理_链表


因为HashMap长度有限(16),当插入Entry越来越多时,散列函数(即哈希函数) 难免出现index 碰撞(冲突)的情况。

hash算法数据结构 mysql hashmap数据结构及原理_十进制_02


这种情况我们就需要使用链表解决;

HashMap数组元素不仅是一个Entry 对象,也是一个链表的头结点。每一个Entry 对象通过Next 指针指向它的下一个Entry节点。如果新插入的Entry对象与之前的对象位置冲突,则会插入到对应的链表:

hash算法数据结构 mysql hashmap数据结构及原理_hash算法数据结构 mysql_03


注:新来的Entry节点插入链表时,使用的是“头插法”。(原因是设计者认为后插入的数据更容易被使用)

2、Get方法原理

当我们使用Get方法根据Key 来查找Value 的时候,

首先会将Key做一次Hash 映射,从而得到对应的index:

index = Hash(“flower”);

hash算法数据结构 mysql hashmap数据结构及原理_链表_04


第一步:我们查看的头结点是Entry6,Entry6 的Key 是 laihama, 不是我们想要的结果。

第二步:我们查看的是next节点Entry1, Entry1 的key 是flower,正是我们需要的结果。

Entry6放在头节点,是因为HashMap的发明者认为,后插入的Entry被查找的可能性更大。这就是HashMap的底层原理。

三、补充

java8中HashMap 的结构优化
1、HashMap每次自动扩展或 手动初始化时,长度必须是2的幂;
2、长度选择16是为了服务于从Key映射到index 的 Hash算法;
index = Hash(“flower”);
如何实现一个尽量均匀分布的Hash 函数,我们通过利用Key的HashCode值做某种运算。
3、为实现高效的Hash算法,HashMap 采用位运算方式。
index = HashCode ( key ) & (Length -1);
eg:我们以值为“book”的Key来演示整个过程
1.计算book的hashcode,结果为十进制的3029737,二进制的1011100011101011101001;
2.假定HashMap长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111。
3.把以上两个结果做与运算,101110001110101110 1001 & 1111 = 1001,十进制是9,所以index=9。可以说,Hash算法最终得到的index结果,完全取决于Key的Hashcode值的最后几位;
4、长度采用16或2的幂的原因:
因为16 二进制为 1111,采用其它进制进行运算时无法保证数据的均匀分布;

单独看这个结果,表面上并没有问题。我们再来尝试一个新的HashCode 101110001110101110 1011

hash算法数据结构 mysql hashmap数据结构及原理_hash算法数据结构 mysql_05


反观长度16或者其他2的幂,Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的;

5、HashMap 中当链表长度超过8 时将会转换为“红黑树”,后期更新博客会有说明;