等于大于号 HashMap 源码详细分析(7)
删除操作本身并不复杂,有了前面的基础,理解起来也就不难了,这里就不多说了。
前面的内容分析了 HashMap 的常用操作及相关的源码,本节内容再补充一点其他方面的东西。
如果大家细心阅读 HashMap 的源码,会发现桶数组 table 被申明为 transient。transient 表示易变的意思,在 Java 中,被该关键字修饰的变量不会被默认的序列化机制序列化。我们再回到源码中,考虑一个问题:桶数组 table 是 HashMap 底层重要的数据结构,不序列化的话,别人还怎么还原呢?
这里简单说明一下吧,HashMap 并没有使用默认的序列化机制,而是通过实现readObject/writeObject两个方法自定义了序列化的内容。这样做是有原因的,试问一句,HashMap 中存储的内容是什么?不用说,大家也知道是键值对。所以只要我们把键值对序列化了,我们就可以根据键值对数据重建 HashMap。有的朋友可能会想,序列化 table 不是可以一步到位,后面直接还原不就行了吗?这样一想,倒也是合理。但序列化 talbe 存在着两个问题:
table 多数情况下是无法被存满的,序列化未使用的部分,浪费空间
同一个键值对在不同 JVM 下,所处的桶位置可能是不同的,在不同的 JVM 下反序列化 table 可能会发生错误。
以上两个问题中,第一个问题比较好理解,第二个问题解释一下。HashMap 的get/put/remove等方法第一步就是根据 hash 找到键所在的桶位置,但如果键没有覆写 hashCode 方法,计算 hash 时最终调用 Object 中的 hashCode 方法。但 Object 中的 hashCode 方法是 native 型的,不同的 JVM 下,可能会有不同的实现,产生的 hash 可能也是不一样的。也就是说同一个键在不同平台下可能会产生不同的 hash,此时再对在同一个 table 继续操作,就会出现问题。
综上所述,大家应该能明白 HashMap 不序列化 table 的原因了。
本章对 HashMap 常见操作相关代码进行了详细分析,并在最后补充了一些其他细节。在本章中,插入操作一节的内容说的最多,主要是因为插入操作涉及的点特别多,一环扣一环。包含但不限于“table 初始化、扩容、树化”等,总体来说,插入操作分析起来难度还是很大的。好在,最后分析完了。
本章篇幅虽比较大,但仍未把 HashMap 所有的点都分析到。比如,红黑树的增删查等操作。当然,我个人看来,以上的分析已经够了。毕竟大家是类库的使用者而不是设计者,没必要去弄懂每个细节。所以如果某些细节实在看不懂的话就跳过吧,对我们开发来说,知道 HashMap 大致原理即可。
好了,本章到此结束。
写到这里终于可以松一口气了,这篇文章前前后后花了我一周多的时间。在我写这篇文章之前,对 HashMap 认识仅限于原理层面,并未深入了解。一开始,我觉得关于 HashMap 没什么好写的,毕竟大家对 HashMap 多少都有一定的了解。但等我深入阅读 HashMap 源码后,发现之前的认知是错的。不是没什么可写的,而是可写的点太多了,不知道怎么写了。JDK 1.8 版本的 HashMap 实现上比之前版本要复杂的多,想弄懂众多的细节难度还是不小的。仅自己弄懂还不够,还要写出来,难度就更大了,本篇文章基本上是在边读源码边写的状态下完成的。由于时间和能力有限,加之文章篇幅比较大,很难保证不出错分析过程及配图不出错。如果有错误,希望大家指出来,我会及时修改,这里先谢谢大家。
好了,本文就到这里了,谢谢大家的阅读!
JDK 源码中 HashMap 的 hash 方法原理是什么?- 知乎
Java 8系列之重新认识HashMap - 美团技术博客
python内置的hash函数对于字符串来说,每次得到的值不一样?- 知乎
Java中HashMap关键字transient的疑惑 - segmentFault
作者:code4fun
为了获得更好的分类阅读体验,
请移步至本人的个人博客:
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。
赶紧抢