Java HashMap 扩容机制怎么工作
java hashmap 的扩容是在元素数量超过阈值(容量乘以负载因子)时触发的,默认将数组容量翻倍并重新分配所有节点。具体做法是调用 resize() 方法,创建一个新的、容量为原来两倍的数组,然后遍历旧数组中的每个链表或红黑树,将节点重新计算哈希位置并放入新数组中。如果使用的是 jdk 1.8+,在并发环境下直接调用此方法会引发 concurrentmodificationexception 或数据丢失,因此必须使用 concurrenthashmap 或在单线程中安全操作。

什么条件会触发 HashMap 扩容
触发扩容的条件是 size > threshold,其中 threshold = capacity * loadFactor。默认负载因子为 0.75,初始容量为 16,因此当插入第 13 个元素时(16 * 0.75 = 12),就会触发扩容。扩容后新容量为 32,新阈值为 24。这一过程发生在 put 或 putAll 方法内部,当检测到需要新增节点且当前大小已达到阈值时执行。

import java.util.HashMap;
public class HashMapResizeDemo {
public static void main(String[] args) {
// 初始容量 16,负载因子 0.75,阈值 12
HashMap<string integer> map = new HashMap(16);
// 插入 12 个元素,不触发扩容
for (int i = 0; i <h2>JDK 1.8 中扩容时红黑树如何调整</h2>
<p>在 JDK 1.8 中,如果某个桶中的链表长度超过 8 且数组容量大于等于 64,链表会转换为红黑树以提高查找效率。扩容时,如果树分裂后子树节点数小于等于 6,红黑树会退化回链表;否则保持为树结构。这是因为小规模的树结构维护开销高于链表,而大规模时使用树能保持 O(log n) 的查找性能。扩容过程中,节点会被重新分配到新数组的两个桶中:原索引位置和新索引位置(原索引 + 旧容量)。</p>
<pre class="brush:php;toolbar:false;">import java.util.HashMap;
import java.util.Map;
public class TreeMapResizeDemo {
public static void main(String[] args) {
// 强制触发树化:容量足够大且冲突多
HashMap<integer string> map = new HashMap(64);
// 插入大量相同哈希值的键,触发树化
for (int i = 0; i map) {
try {
java.lang.reflect.Field f = HashMap.class.getDeclaredField("table");
f.setAccessible(true);
Object[] table = (Object[]) f.get(map);
int count = 0;
for (Object entry : table) {
if (entry instanceof java.util.HashMap.Node) {
java.util.HashMap.Node, ?> node = (java.util.HashMap.Node, ?>) entry;
if (node.treeNode != null) {
count++;
}
}
}
return count;
} catch (Exception e) {
return 0;
}
}
static class CustomKey {
private final int value;
CustomKey(int v) { this.value = v; }
@Override public int hashCode() { return 1; } // 固定哈希值
@Override public boolean equals(Object o) {
return o instanceof CustomKey && ((CustomKey) o).value == this.value;
}
}
}</integer>
扩容时节点在新数组中的位置如何确定
扩容时,每个节点在新数组中的位置取决于其原始哈希值和旧容量的关系。具体来说,如果节点的哈希值与旧容量进行按位与运算结果为 0,则节点留在原索引位置;否则,节点移动到新索引位置(原索引 + 旧容量)。这种优化避免了重新计算整个哈希值,提高了扩容效率。例如,旧容量为 16(二进制 10000),如果节点哈希值的第 4 位为 0,则留在原位;否则移动到原索引 + 16 的位置。
import java.util.HashMap;
import java.util.Map;
public class NodePositionDemo {
public static void main(String[] args) {
HashMap<integer string> map = new HashMap(4); // 初始容量 4
// 插入几个元素
map.put(1, "one");
map.put(5, "five"); // 1 和 5 在容量 4 时可能冲突
map.put(9, "nine");
System.out.println("Before resize:");
printPositions(map);
// 触发扩容
map.put(13, "thirteen");
System.out.println("After resize:");
printPositions(map);
}
private static void printPositions(HashMap<integer string> map) {
try {
java.lang.reflect.Field f = HashMap.class.getDeclaredField("table");
f.setAccessible(true);
Object[] table = (Object[]) f.get(map);
for (int i = 0; i node = (java.util.HashMap.Node, ?>) table[i];
System.out.println("Index " + i + ": Key=" + node.key + ", Value=" + node.value);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}</integer></integer>
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











