HashMap实现原理(详解含源码解析)

引入:已经有了TreeMap,为什么还要有HashMap?

如果数据比较少,使用TreeMap存储,红黑树还需要旋转、变色等操作,有点大材小用;

数据量少点情况下,直接使用数组最为简单;随着时间的推移,存储的数据越来越多,数组达到瓶颈;

如果直接使用红黑树,数据量越大,所需要的旋转、变色也就越多;可见,没有一种数据结构能满足不同状态的所有需求

需要选择多种数据结构来满足需求。

HashMap结构

数组+链表+红黑树(默认空间为16)

        HashMap 的内部结构是一个数组,这个数组的每一个元素都是一个链表或树(在 Java 8 之后,链表长度超过一定阈值时会转为红黑树)。每个键值对存储在数组的某个位置(即桶位)中。如果多个键的哈希值对应相同的桶位,那么这些键值对会被放在同一个链表或树中。

初始状态:

        数组:HashMap的底层是一个数组(即哈希表),每个数组元素是一个桶,这些桶刚开始都是空的。(主体

        链表:每一个桶其实是一个链表的头节点(或数根节点),一开始桶内没有元素时,链表也是空的。(为了解决hash冲突

存储结构:

存储元素时:

        元素存储:当你向HashMap中插入一个元素时,HashMap会根据键的hashCode()计算该元素应该存放在哪个桶(数组的索引位置)中。

        链表初始化:如果这个桶是空的(即这个位置上没有任何元素),HashMap 会在这个位置创建一个新的链表节点,并将元素存储在该链表节点中。

        链表存储:如果该桶已经有元素(链表节点)了,会进行key. equal进行判断,key值相等则进行替换,不相等则会以链表的形式追加到这个链表的末尾。

hash算法

        在进行数据插入时(默认大小为16),需要获取一个0-15的索引,获取索引之后,将数据按照索引的位置插入,索引该如何获取???

        后续还需要使用索引从中得到数据,所以每一个数据需要保证同一个key每次生成的索引值是相同的

方法一(jdk1.8之前):对插入的key进行hash计算之后对16进行取余(效率较低

方法二:使用hash&(16-1)(哈希碰撞较高)

方法三:使用扰动函数减少哈希冲突,高16位和低16为进行异或运算之后得到新的hash值,在与n-1进行&运算

        在计算机科学,尤其是在哈希表的实现中,“扰动函数”(perturbation function)通常是用来进一步混淆哈希值,以减少哈希冲突的可能性,确保哈希值在数组中的分布更加均匀。

作用和目的:

1. 减少冲突:在哈希表中,如果两个不同的键被哈希到相同的索引位置,就会发生哈希冲突。虽然好的哈希函数可以尽量避免冲突,但在实际应用中难免会有冲突的情况。扰动函数通过对初始哈希值进行进一步的扰动,减少了哈希冲突的发生。

2. 分布均匀:扰动函数有助于将相似的哈希值分布到不同的桶(数组索引)中,确保哈希表的数据分布更为均匀,从而提高哈希表的查找效率。

        扰动函数是哈希表中的一种技术手段,通过进一步处理哈希值,减少冲突并提高哈希表中数据分布的均匀性。虽然它不是所有哈希表实现中的必备部分,但在一些对性能要求高的哈希表实现中(如 HashMap),它起到了重要的优化作用。

HashMap扩容

扩容因子:0.75

也就是说当元素个数达到数组的3/4,也就是9个的时候,数组会进行扩容,扩容为当前的一倍32

数据迁移:在扩容之后,需要对原来链表中的数据进行数据迁移,所有数据需要重新进行哈希计算,并迁移到新的数组中。

为什么数组长度扩容需要为2的幂次方?

在进行hash计算的时候,使用数组长度-1的二进制值进行&运算,若数组长度-1为偶数,则会出现问题

在HashMap中,虽然可以设置初始化值,但是HashMap并不会按照设置的值作为初始值,而是会使用设置值的最接近的2的幂次方作为初始值。

红黑树转换

        在数组长度达到一定长度,使用扩容不再能够达到解决链表增加的情况时。也就是在数组长度大于等于64不能够进行扩容,链表长度大于等于8,将链表转换成对应的红黑树;在红黑树的节点小于6的情况下(否则在临界点增删元素时,会导致红黑树和链表之间来回切换),红黑树会转变为链表

红黑树详解

CSDN

HashMap源码解析

插入数据时计算key所对应的hash值

hash(key):获取key的哈希值,并将所得到的哈希值进行无符号右移16位,与哈希值进行异或运算得到新的哈希值

key.hashCode():被标记为 @IntrinsicCandidate 的方法可能会在 JVM 内部使用专门的优化或实现,直接在本地代码(如 C++ 或汇编)中执行,而不是通过 Java 方法调用的常规机制。

Node:

putVal:

resize():

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值