HashMap的底层
bobo老师,你好,我们知道,HashMap 的底层实现是 数组 + 链表。当链表长度大于8时,如果数组长度小于64,则会选择扩容数组;如果数组长度大于64,会将链表转换为红黑树。
问题:这里链表的长度为什么会是8?数组长度为什么会是64呢?
经过搜索后得到了部分答案:链表长度为8是为了减少红黑树的出现,因为链表转换为红黑树是相对耗时的,为什么会是这样呢?以及数组长度为64该如何解释
正在回答
这里的 8 和 64 不是计算的结果,不是逻辑推导的结果,而是人为设置的。这类参数在算法领域被称为是“超参数”。如果一定要问为什么,就是“经验表明,在大多数情况下,使用这些参数都能使得算法和数据结构的表现在统计意义下是良好的。”
实际上,在现实生活中也存在很多超参数。比如围棋棋盘为何纵横都有 19 条线?
扑克牌为什么是 4 种花色?每种花色 13 张牌?为何只有两张王?
等等。
如果你接触过机器学习,在机器学习领域,则近乎到处都是超参数。比如解决某个问题的神经网络为什么是 x 层?每层为什么有 y 个“神经元”?等等。
即使在传统算法领域里,也有很多超参数的例子,在这个课程的后续,我会拓展大家对前面学习的所有的算法和数据结构的思考,你将会看到,为什么一定是二叉树?三叉树四叉树完全可以;为什么归并排序每次要二分?三分,四分,甚至 256 分,完全可以;等等等等,并且你将看到,在一些情况下,多分二叉树,是优于二分的。实际上,我们通常接触的算法和数据结构中,二叉树中的 2;二叉堆中的 2;归并排序中每次 2 分数组的 2,都是超参数。
==========
回到你的问题,对于哈希表的设计来说,超参数不仅仅是这些。对于哈希表来说,最最最最最最重要的一个超参数,其实是哈希函数是什么。(是的,超参数不一定是一个数,也可以是一个函数,甚至是一个流程(pipeline))
因为在理想状态下,哈希表应该不存在哈希冲突,即使存在冲突,冲突率应该极其低。在一个 bin 中出现 8 个元素,在某种意义上这个哈希存储已经不是很合格了,更优的解决方案应该是换哈希函数,而不是纠结此时这个 8 个元素的存储方式了。这也就是为什么,对于很多语言来说,其标准库的实现中,哈希表并不会引用这个机制。因为其默认在哈希冲突过高的情况下,开发者应该设置自定义的更好的哈希函数。
也正是因为这个原因,对于哈希表来说,最重要的讨论其实都集中在哈希函数的设置上,而非这个细节。当然,这个细节是有意义的,因为这个细节使得 Java 的哈希表的复杂度不会退化到 O(n),最差到 O(logn)。但依然是,解决哈希表可能的退化问题的根本之道,是根据实际问题,去寻找更好的哈希函数。
P.S.这是一篇讨论 C++ 标准库中的默认哈希函数在何时会退化,如何更好地避免这种退化的非常经典的 blog:https://codeforces.com/blog/entry/62393
更系统地讲解哈希函数的内容,通常数学性较强,从计算机科学的角度来看,通常在和安全,密码学等相关的教材中做更详细的讨论(在安全领域,和哈希相关的攻击种类非常多,但整体应用的原理都是尽量去产生哈希冲突);更底层则属于数论,离散数学等数学领域的内容。
有一些专门介绍和哈希算法相关的书籍,有兴趣可以找一找,比如这种:https://doc.lagout.org/science/0_Computer%20Science/2_Algorithms/Design%20of%20Hashing%20Algorithms%20%5BPieprzyk%20%26%20Sadeghiyan%201993-11-23%5D.pdf
继续加油!:)
恭喜解决一个难题,获得1积分~
来为老师/同学的回答评分吧
0 星