《怒肝JAVA不慌系列》:HashMap从JDK1.7到JDK1.8异同

在开始这篇之前或者这个系列之前,其实离上次更新文章有段距离了;没写文章的这期间 因为你看文章老没给一键三连 除了忙 ,其中更多的是对我文章和写文章这个事情有很多的思考,其中最主要原因在于这些文章能否给我或者给正在看文章的你带来价值,让我内心产生了波澜。但是呢,仔细想想,如果在搞技术的路上,不能留下些东西岂不是枯燥无味;所以呢,以后的文章可能会很慢,但是都不再是单单一味追求篇数、粉丝、点赞、收藏等等。我只想认真写我的,剩下的都是结果所以我都不管。

让我们从一个个疑问中出发,去找答案,问来问去,面试不都问这些吗?

HashMap涵盖了Java集合非常经典的算法和数据结构,以至于成了面试几乎必问的一个问题;HashMap也有很多应用场景,例如很多本地缓存的底层都是用HashMap。

HashMap作为Map类型的集合最大的优点在于查找效率比较高,理想情况下可以实现 O(1)的 时间复杂度

什么是时间复杂度?

算法复杂度的一种,算法复杂度分为:时间复杂度和空间复杂度。时间复杂度是度量算法执行时间长短,而空间复杂度是度量算法所需占用空间大小。

什么是O(1)的时间复杂度?

想找到一个元素,只需找一次。

而常见的O(n) 呢?

在无序数组中找一个元素需要挨着对比每一个,然后看是不是想要找的,最坏的情况是有多少个元素就找多少次。

HashMap是基于什么样的机制?

如果有给定一个初始值,就会用给定的初始值,否则默认为 0;只有真正对数据进行添加put时,才分配默认 16 的初始容量。

也就是put的时候才创建数组。(避免了浪费空间)

HashMap是什么时候扩容的?

HashMap是什么时候扩容的?

有两个因素:

  • Capacity:HashMap 当前容量)

  • LoadFactor:负载因子,默认值 0.75

也就是 16 * 0.75 = 12,这意味着数组最多只能放12个元素,一旦超过12个元素,哈希则需要扩容。再举一个例子:假设你当前容量是100,那么当你存进第76个时就进行扩容。

怎么扩容的?

有为两步:

每次扩容2倍,也就是创建一个新的Entry空数组,长度是原来数组的2倍。

遍历原来的数组,把所有重新Hash到新数组。

作为集合HashMap不得不从底层开始。

  • JDK1.7的时候底层是基于数组+链表。

为什么用数组,什么是数组?

用过的小伙伴都知道,首先数组是用来存储数据,长度是固定不变。其次数组是连续的内存空间地址,遍历效率高,所以查询效率也高。

什么是链表,用来解决什么问题?

链表其结构是,元素之间串联起来且每个元素为一个节点,链表在内存中的空间是非连续、非顺序的;每个节点包含两部分,一个存储的数据,一个是下一个节点地址。链表插入和删除数据速度快,是只需要修改相邻节点的指针域就行。

为什么使用链表?因为哈希表底层是数组,数组长度是有限的,当插入的数据相同时,就产生了 哈希冲突

使用链表最主要是用来解决哈希冲突的,因为哈希本身存在概率性,例如当 “一颗剽悍的种子” 或者 “种子剽悍的一颗” 的hash有一定概率会一样,如下图在极端情况下hash到了一个值上,那么就形成了 链表

所以什么时候形成链表?就是在哈希冲突的时候产生。
在这里插入图片描述
而链表过于长会导致查询的效率,由常数阶变成线性阶。

常数阶是指与问题的大小无关(就是不管它O(n)里n是多少),执行的时间都是恒定的,我们称具有 O(1) 的时间复杂度,又叫常数阶。

线性阶是指执行时间随问题规模增长呈正比例增长,也就是 O(n)

因此在JDK1.8的时候,很巧妙的引入了黑红树将原本O(n)的时间复杂度降低到了O(logn)。

所以 JDK1.8底层是数组+链表+红黑树

与其说JDK1.8底层是数组+链表+红黑树,不如说是JDK1.8的链表会转化为红黑树。

链表什么时候会转成红黑树呢?

当数组大小大于64且链表大小大于8的时候链表才会转化成红黑树。而当链表小于6的时候,红黑树就会退化为链表。而红黑树退化链表操作的主要可以说是查询和插入时对性能的考量。链表查询时间复杂度是 O(n),插入是O(1) ,而红黑树查询和插入时间都是一样O(logN)

在这里插入图片描述

那么问题又来了,什么是红黑树?

限于篇幅原因,简单的说红黑树就是一种自平衡的二叉树,通过左旋或者右旋,调节状态已达到平衡。每个节点要么是红色,要么是黑色。根节点必须是黑色。红色节点不能连续,也就是,红色子节点和父节点都不能是红色。对于每个节点,从该点到树尾端的路径,都含有相同个数的黑色节点。

HashMap在JDK1.7和JDK1.8的区别?

除了上面所说JDK1.7的底层是数组+链表,而JDK1.8是数组+链表+红黑树之外。JDK8将使用头插改为尾插。

而为什么将头插改为尾插呢?

很容易理解,因为使用头插会改变链表的顺序,而使用尾插,在扩容是就会保持链表元素原本的顺序。

最后HashMap是安全的吗?当然不是,简单说就是多个线程同时去访问一个资源而产生线程不安全的原因。

最后

最后的最后,为了更好的阅读体验,我把想说的话都放在了下面,嘿嘿。

我是一颗剽悍的种子 把我会的,认真的分享 是我写博客一直不变的信条。
如果你能看到这篇博文,说明咱们还是很有缘的;希望能带给你一些许帮助,创作的不易,
把我文章的知识带走,你的三连留下,点赞,评论,关注,是我最大的动力。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值