当HashMap存储元素时,其底层原理涉及以下几个关键步骤:
1. 计算哈希值
如下图所示,首先进入put方法,put方法返回值调用putVal方法,putVal方法中第一个参数又调用了hash方法,在hash方法中,根据key计算出要添加的这个元素的hash值,是一个int类型的整数,它代表了键对象在哈希表中的位置。
2. 确定存储位置
计算出hash值后,就该进入到putVal方法中了。
在确定存储位置之前,有一些初始扩容操作,如下图所示。
让我们再进入到resize方法中。
这是要用到的几个常量的值。
如上图所示,第一次添加元素时,HashMap使用数组存储元素 ,初始化容量为16,初始threshold(阈值)为12。
然后就是确定存储位置了,让我们再返回到putVal方法中,如下图所示。 HashMap使用hash值和HashMap内部的数组长度来确定元素的存储位置。
3. 处理冲突
(1)当不同的键具有相同的hash值,即发生hash冲突时,HashMap使用链表或者更高效的红黑树来处理冲突。如下图所示。
(2)在链表上,冲突的键值对会被添加到同一位置的链表中。在红黑树上,冲突的键值对会被添加到同一位置的树结构中。
(3)从下图也可以看到,一旦确定了存储位置,HashMap会将键值对存储在对应的数据结构(链表或红黑树)中。如果键对象已经存在于HashMap中,则新的值会覆盖旧的值。
将元素存在链表中:
将元素存在红黑树中的treeifBin方法。
5. 扩容(Resize)
HasnMap会在三种情况下进行扩容:
(1) 当创建一个新的 HashMap 实例时,会初始化一个初始容量为16的数组用于存储键值对(上述确定存储位置章节已介绍过)。
(2)如果HashMap的实际存储元素数量达到了负载因子乘以当前数组长度的阈值(默认负载因子为0.75),HashMap会进行扩容操作。如下图所示。
(3)若数组的长度>8且<64时,HashMap会进行扩容操作(在treeifyBin方法中),如下图所示。
在(2)和(3)中, HashMap会进行扩容操作如下图所示,数组长度扩容到原来的两倍,阈值也增加到原来的两倍。
5.总结:HashMap的数据结构
由前面可知,HashMap是基于哈希表实现的,它的数据结构主要由数组、链表和红黑树组成。
(1)数组:
①HashMap 的主体是一个数组。
②元素数量是固定的,由HashMap的容量决定。默认情况下,HashMap的初始容量是 16。
2. 链表和红黑树:
①HashMap也使用链表或者红黑树来存储键值对。
②当链表长度超过阈值(默认为 8)时,链表会被转换成红黑树,以提高查找、插入和删除操作的效率。
③这种数据结构的选择(链表还是红黑树)取决于键值对数量的多少和性能的需求。
总结来说,HashMap 的核心数据结构是一个基于数组的哈希表。通过链表或红黑树来解决哈希冲突,HashMap 能够提供高效的插入、查找和删除操作,具有接近常数时间复杂度的性能。