浅析HashMap

浅析HashMap

前言

何为HashMap,Hash即哈希算法,Map即映射,HashMap即借助哈希算法达到快速存取key-value键值对(一个key映射一个value)的集合

存储结构

HashMap使用数组加链表的方式存储。数组的好处在于能够实现随机访问,知道位置,查找的时间复杂度可以达到O(1),那如何快速确定元素在数组中存储的位置呢。

哈希算法

HashMap通过使用哈希算法获取到key的hash值,并通过取模的方式获取到该元素在数组中的位置。前面说到,HashMap使用数组存储的目的是为了达到时间复杂度为O(1)的查找速度。那自然对确定元素位置的hash算法的运行速度要求非常快,要不然使用数组的优势将荡然无存,并且为了对每次对同一个key的访问都能找到同一个元素,则hash算法必须满足另外一个要求,对同一个值每次计算得到的结果必须相同。由以上可总结出哈希算法在HashMap有以下作用:
1. 哈希函数不能过于复杂,要求有很高的运算速率
2. 哈希函数对同一个入参每次计算结果必须相同
3. 哈希函数对于不同的入参尽量避免哈希值重复,以减少哈希冲突
注:哈希函数:对key计算哈希值并算法最终位置的过程
补充一点在HashMap中未分析到的特点:不能轻易通过对哈希值进行分析得到原数据(此处主要针对网络安全相关,后续有空会出相关文章)

哈希冲突

从上述过程中,可以得知对HashMap的存取过程。通过对key计算哈希值得到在目标数字的位置,通过数组的随机访问,快速找到目标key。此处抛出一个疑问。当两个key计算的哈希值,或者算得在数组位置相同怎么办,该种情况即本章要说的哈希冲突。
学过java的人都知道,当两个key在数组中同样的位置,其中一个key放在数组中,另外一个或者更多key通过链表的方式与数组中key链接起来。至于哪个key在链表,哪个key在数组,这点在jdk8有所改变,此处不再缀叙,直接说结论,jdk8中,第一个占据此位置的元素在数组,其他重复占位的元素在链表。jdk8之前,最后一个占据此位置的元素在数组,其他重复占位的元素在链表。

链表存在的问题

众所周知,链表的查找效率是O(n),如果链表比较长,是不是和前面数组随机访问的O(1)时间复杂度相去甚远,有没有什么快速访问的办法呢。
方法其实有很多,但是各有弊端,此处不展开讨论,直接说结论,java在链表超过一定长度后,会采用二叉树来处理链表查询慢的问题。
普通二叉树遍历并不会增加查找效率,故而考虑引进二叉查找树,通过二叉查找树的特性:左子节点<父节点<右子节点,可以在通常情况下,达到O(logn)的时间复杂度。
我们知道,jdk8对于该链表数据的存储用的是红黑树,和我们分析的情况出现了偏差,继续探究原因。
二叉查找树通常情况下,查找的时间复杂度确实为O(logn),这只是比较理想的情况下,在一些极端的情况下,根节点选择的并不合适,其他节点全部大于或者小于更节点,此时二叉查找树会退化成普通单链表,时间复杂度重新变为O(n)。
二叉查找树退化单链表的常用解决方案是改用平衡二叉查找树,该树具有自平衡的功能,通过调整树的结构和根节点的位置,能尽量保证左右节点数量平衡,这样就能使这条单链表保持O(logn)的时间复杂度。红黑树就是一种近似的平衡二叉查找树,答案已经很接近了。
之所以使用红黑树,不使用平衡二叉查找树的原因也很简单,平衡二叉查找树在每次增加删除数据之后,需要做平衡操作,操作也比较复杂,比较消耗资源,红黑树在保持平衡这方面有很大的优势,红黑树具体结构和维持平衡的方式后续单独写文章说,一两句说不清。

数组扩容

通过上面那条链表的分析上,我们知道,那条链表怪复杂的,性能也远比不上在数组中操作,所以我们要尽量避免数据出现过多的哈希冲突,避免大量数据堆积到链表中,对哈希冲突的解决方法无非两点:
1.优化哈希函数,尽量避免哈希冲突
2.数组扩容,数组变大,出现冲突占据同一位置的概率就会大大缩小
对于第二点,数组扩容,jdk设置了一个加载因子(默认0.75,可设置),当HashMap中存储的元素达到数组大小的一定比例的时候,会触发扩容,默认扩容大小为2倍,扩容后,需要重新计算所有元素的哈希值,重新确认元素在该数组的位置。

指定初始大小

在一些代码规范插件中,一般都会提示开发者指定HashMap容器的大小,默认情况下,新创建的HashMap大小为16,但是实际上,
1.我们可能一次性用很少几个,完全用不上长度为16的数组,数组是连续的存储空间,还是比较稀缺的资源
2.我们知道此处HashMap会存有大量数据,此时可以把初始大小设置大一点,避免每次扩容时重新计算元素的位置。
介于上述情况,还是显式的指明初始数组大小比较合理

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值