HashMap原理

java7中HashMap

需要了解的知识点

1.HashMap原理
2.为什么HashMap的容量一定要2的n次方
3.在java7中,HashMap的两个致命问题

HashMap原理

HashMap的初步模型

想象一下,一条绳子上绑着16个桶,每个桶都有各自的编号,假如你要找13号,直接在13号桶上拿东西就好,不用从第一个开始数,像这样在这里插入图片描述
很好,其实这就是数组,他的存放形式如:[数字,对象],即,我可以通过数字找到其相对应的对象,但是,这样很不方便,因为我必须得通过数字去查找,那么,现在我想改进一下,通过字符串去查找,即存放形式为[字符串,对象],是否可行?答案显然易见的,这就是hash表。
那么现在有个问题就是,我要怎么直接通过字符串找到我想要的对象呢,用两个数组实现?这也太蠢了,而且效率还贼慢。这时候,我们就要靠hashCode这个方法了
在这里插入图片描述
这个方法是通过object继承过来的,每个对象都会有的一个方法,那么这个方法有什么用呢?我们来运行一遍在这里插入图片描述
咦,这就很神奇了,“123456”这个字符串竟然转换成了数字1450575459,那么我们上面的通过[字符串,对象],这种存放的形式就可以实现了,我们只要拿1450575459作为下标,然后将这个对象存放到1450575459这个下标中就可以了。
设想很完美,但是,我们知道,数组在内存中的空间是连续的,难道我们要开辟一个1450575459那么大的空间吗?这样是不是极其浪费,浪费可耻。那么我们有什么方法可以解决这个问题呢?

避免浪费空间的解决方案

废话不多说,直接切入主题,在我们学习java基础中是不是学习过一个<按位与>操作(在学习的时候,我感觉这个东西一点用都没有),即,我们可以使用1450575459 & 15,我们来看看结果:
在这里插入图片描述
这就很神奇了,他们按位与的结果是3,怎么回事,难道是凑巧,我们可以多试换几个数 & 15 ,发现,怎么换,结果都是在15内,那不就可以让我们拿来当下标使用了吗?而且也不用开辟那么多的内存空间。
但是,到底为什么呢?我们来看一下,将15和1450575459都转换成二进制,然后进行按位与操作
在这里插入图片描述
0011转换为10进制就是3,非常好,破案了,但是新的问题又产生了,为什么是15,我可不可以14,7或者其他?

为什么是& 15

就按位与的操作来说,无论你与上哪个数都是可以的,但是,按需求分析我们需要达到的效果,就必须得2的n次方减1,为什么?我们先来看看我们要达到的效果:
1.将得到的结果保证再数组大小范围内
2.能覆盖到0-15每个数字
我们来分析一下,

	第一个需求,只要是比15小的数字都能满足。
	第二个需求,我们来观察一下,15的二进制是(1111),31的二进制是(11111),63的二进制是(111111),很好,我们发现一个规律了,全都是1。
接下来我们看看按位与操作,1 & 1 = 1,1 & 0 = 0,0 & 0 = 0,很好,破案,也就是说,无论是什么数字与15进行按位与操作,我们都能留下这个数字的二进制的最后4位,即:1001 & 1111 = 1001,1011101 & 1111 = 1101

这种情况下,我们就两个需求都满足了

初步实现HashMap

我们现在已经初步实现了hashMap,[对象,对象]模式,即[key,value],我们可以:
第一步,我们需要获取key的hashCode
第二步,通过key的hashCode计算出其所在的下标
第三布,根据这个key的下标,将value放到对应的桶的位置
但是,还有一个问题没有解决,假如,在一个Map中,出现了两个key算出来的下标是一样的(即hash冲突),怎么办?

解决hash冲突

我们上面的假设是一个桶放一个对象,所以出现了hash冲突不知道怎么办,但是,假如说我桶里面放的是一堆对象呢?我们就不用考虑这个问题了,只要是相同下标的,都往同一个桶里面丢,但是我们要找的时候要怎么找呢?在java7中,假如发生了hash冲突,是按照链表的结构进行存放的,很好,在链表中,我们只要有头一个Node就可以找到下一个,然后一直找下去,直到找到你需要的那个对象。

扩容

在使用HashMap的时候,会有一个默认容量,为16,另外还有一个loadFactor值为0.75的扩容因子,什么意思呢?

loadFactor的作用

就是当map中的元素数量达到总容量的75%的时候进行扩容,为什么是75%呢?这里我找了一篇讲这个的文章,可以参考一下。https://blog.csdn.net/CSDN4006600/article/details/100691122

扩容的过程

第一步,我们需要重新创建一个数组,这个数组的容量是前一个数组的容量*2,
第二步,将旧的数组中的值一个个取出来,重新计算hash值,重新分配位置
所以说,hashMap是一个极其消耗效率的过程,因此,如果知道你需要使用的Map的大概容量,最好是能指定其容量大小,这样能节省不少效率
上面的就是java7中的HashMap

java7中的HashMap可能发现的严重问题
可能造成死锁

这个问题有点复杂,我在这里就不详细讲,有兴趣的朋友可以搜索一下coolshell hashmap,或者看一下这个链接https://blog.csdn.net/gyflyx/article/details/17286973

造成tomcat可能被DOS恶意攻击

为什么会造成DOS攻击呢?首先,前提条件,tomcat中大量使用了HashMap。怎么造成的攻击?

我们已经知道了hash冲突,我们假设一种比较极端的情况,假如我们所有的key计算出来都是同一个值,这样就造成了大量的hash冲突,然后会发生什么事情呢?我们的HashMap本来查找效率O(1),当我们全部都在同一个下标的时候,我们的HashMap就相当于退化成了链表结构,查找效率就退化成了O(n),我们使用HashMap的目的是什么,就是因为它的高效,但是,现在退化成了O(n)最差级别的查找效率了,当HashMap在查找的时候就会耗费大量的效率,就会造成效率低下。

特殊情况我们已经了解了,我们回归实际,我们可以造成那么大量的hash冲突吗?其实是可以的,我们只要刻意为之,我们能找到很多能产生哈希冲突的字符串。这就给了黑客们一种可能。

java8的改进

这里只说两个比较重要的改进
1.数组+链表 ==》 数组+链表/红黑树(减轻DOS攻击的压力)
2.扩容是插入顺序的改进(减少死锁的概率)

数组+链表 ==》 数组+链表/红黑树(减轻DOS攻击的压力)

在java8中,当同一个桶中的数量超过8时,就会从链表转换成红黑树,为什么是8,参照https://blog.csdn.net/baidu_37147070/article/details/98785367,我们知道红黑树的查找效率为Olog(n),比O(n)快很多,所以,这样做能减轻hash冲突造成的服务器负担

扩容是插入顺序的改进(减少死锁的概率)

我们只要了解是怎么造成的死锁就能知道为什么要改进插入顺序,这个有兴趣的朋友

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值