hashmap用(table.length – 1) & hash计算数组下标,要求容量为2的幂以保证位运算等效取模;hash值经扰动处理,链表长度≥8且数组长度≥64才树化;key必须正确实现hashcode和equals。
HashMap怎么算出数组下标?不是用 % 取模
很多人以为 index = hashCode % table.length 是 HashMap 定位桶的逻辑,其实不是。JDK 8+ 实际用的是位运算:(table.length - 1) & hash。前提是数组长度必须是 2 的幂(如默认 16、32、64…),这样 length - 1 就是形如 0b1111 的掩码,位与操作比取模快得多,也避免负哈希值导致的索引越界问题。
但这也意味着:如果你传入非 2 的幂的初始容量(比如 new HashMap(17)),HashMap 会自动向上找最近的 2 的幂(这里是 32)——所以别硬凑奇数容量,白费力气。
- 自定义 key 类时,
hashCode()要尽量分散,否则高位信息丢失严重,加剧碰撞 - 如果 key 是字符串,Java 已优化过其
hashCode(),一般不用重写;但若 key 是自定义对象,且参与 equals 比较的字段没全用于计算 hashCode,就会出 bug - 注意:
hash()方法在 JDK 中还会对原始hashCode做一次扰动(高 16 位异或低 16 位),进一步降低低位冲突概率
为什么链表长到 8 就转红黑树?不是 7 也不是 9
这个阈值 TREEIFY_THRESHOLD = 8 是经过数学推导和大量压测平衡出来的:泊松分布下,当负载因子为 0.75 时,链表长度达到 8 的概率已低于百万分之一。换句话说,正常均匀哈希下,几乎不会触发树化——它防的是极端碰撞场景,不是日常使用。
但光长度够还不行,还得满足另一个条件:table.length >= MIN_TREEIFY_CAPACITY(即 ≥ 64)。否则即使链表超长,也只扩容,不树化。这是为了防止小数组+短链表就强行树化,反而增加内存和维护开销。
立即学习“Java免费学习笔记(深入)”;