JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动?
·
JDK 1.8 对 HashMap 除了红黑树优化外,还进行了多项重要改动:
1. 数据结构层面的改动
1.1 节点类层次结构重构
// JDK 1.7: 单一 Entry 类
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
}
// JDK 1.8: 多态节点体系
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
// 红黑树节点
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent;
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;
boolean red;
}
1.2 数组初始化时机优化
// JDK 1.7: 构造函数中立即初始化数组
public HashMap(int initialCapacity, float loadFactor) {
this.loadFactor = loadFactor;
threshold = initialCapacity;
table = new Entry[initialCapacity]; // 立即分配内存
}
// JDK 1.8: 延迟初始化 (Lazy Initialization)
public HashMap(int initialCapacity, float loadFactor) {
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
// table = null; // 延迟到第一次 put 时才初始化
}
// 第一次 put 时才初始化
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
if (oldTab == null) {
// 延迟初始化
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
}
}
2. 哈希计算优化
2.1 简化哈希扰动函数
// JDK 1.7: 多次扰动
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
// JDK 1.8: 简化为一次异或
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
优化原因:
- 减少计算开销
- 高 16 位异或操作足以保证哈希分布
- 配合红黑树优化,不需要过于复杂的扰动
2.2 空键处理优化
// JDK 1.7: 空键单独处理
if (key == null)
return putForNullKey(value);
// JDK 1.8: 统一处理,hash 值为 0
int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
3. 扩容机制优化
3.1 扩容后元素重新分布优化
// JDK 1.7: 重新计算所有元素的索引
void transfer(Entry[] newTable) {
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newTable.length); // 重新计算索引
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
// JDK 1.8: 高效重定位,只需判断新增位
if ((e.hash & oldCap) == 0) {
// 索引不变
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
} else {
// 新索引 = 原索引 + oldCap
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
性能提升: 扩容时不需要重新计算所有元素的 hash,只需检查新增的那一位
3.2 扩容阈值计算优化
// JDK 1.7: 简单的容量乘法
threshold = (int)(newCapacity * loadFactor);
// JDK 1.8: 考虑树化情况
newThr = (int)(newCap * loadFactor);
// 如果树化,阈值会调整
4. 并发安全性改进
4.1 减少并发修改异常
// JDK 1.7: modCount 在多处修改
// 容易在迭代过程中抛出 ConcurrentModificationException
// JDK 1.8: 优化 modCount 的使用时机
// 减少不必要的 modCount 递增
4.2 内部迭代优化
// JDK 1.8: 新增 forEach 方法
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K,V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
5. 新增 API 和功能
5.1 compute 系列方法
// JDK 1.8 新增
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)
// 使用示例
map.computeIfAbsent("key", k -> "default value");
map.compute("key", (k, v) -> v == null ? "new" : v + " updated");
map.merge("key", 1, Integer::sum);
5.2 replaceAll 方法
// JDK 1.8 新增
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K,V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
throw new ConcurrentModificationException(ise);
}
v = function.apply(k, v);
entry.setValue(v);
}
}
5.3 getOrDefault 方法
// JDK 1.8 新增
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
6. 性能优化细节
6.1 减少空指针检查
// JDK 1.7: 多次空指针检查
if (table == null || table.length == 0) {
// ...
}
// JDK 1.8: 优化空指针检查逻辑
// 在关键路径上减少不必要的检查
6.2 位运算优化
// JDK 1.8: 更多使用位运算替代算术运算
// 例如: (n - 1) & hash 替代 hash % n
7. 内存优化
7.1 节点对象优化
// JDK 1.8: 节点字段布局优化
// 减少对象头开销,提高内存利用率
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; // hash 放在前面
final K key;
V value;
Node<K,V> next;
}
7.2 延迟初始化节省内存
// JDK 1.8: 延迟初始化避免不必要的内存分配
// 对于创建但不立即使用的 HashMap 节省内存
8. 代码质量改进
8.1 更好的注释和文档
// JDK 1.8: 增加了详细的注释说明
/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;
8.2 代码结构优化
// JDK 1.8: 方法拆分更合理
// 将复杂逻辑拆分为多个小方法,提高可读性
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
final Node<K,V> getNode(int hash, Object key)
final void treeifyBin(Node<K,V>[] tab, int hash)
9. 与 Lambda 表达式集成
// JDK 1.8: 完美支持 Lambda 表达式
Map<String, Integer> map = new HashMap<>();
// 使用 Lambda 遍历
map.forEach((key, value) -> System.out.println(key + ": " + value));
// 使用 Lambda 替换值
map.replaceAll((key, value) -> value * 2);
// 使用 Lambda 计算
map.computeIfAbsent("newKey", k -> k.length());
10. 序列化优化
// JDK 1.8: 优化序列化机制
// 减少序列化时的开销
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
// 优化后的序列化逻辑
}
改动总结对比表
| 改动类别 | JDK 1.7 | JDK 1.8 | 优势 |
|---|---|---|---|
| 数据结构 | 纯链表 | 链表+红黑树 | 防止性能退化 |
| 初始化 | 立即初始化 | 延迟初始化 | 节省内存 |
| 哈希计算 | 4次扰动 | 1次异或 | 提升性能 |
| 扩容重定位 | 重新计算hash | 位运算判断 | 高效重定位 |
| API | 基础方法 | compute/merge等 | 更便捷 |
| Lambda | 不支持 | 完全支持 | 函数式编程 |
| 并发安全 | 较弱 | 改进 | 减少异常 |
总结
JDK 1.8 对 HashMap 的改动是全方位的:
- ✅ 性能优化: 红黑树、延迟初始化、哈希计算简化
- ✅ 功能增强: 新增 compute、merge 等 API
- ✅ 内存优化: 延迟初始化、节点布局优化
- ✅ 代码质量: 更好的结构、注释、可读性
- ✅ 现代特性: Lambda 支持、函数式编程
- ✅ 安全性: 防止哈希碰撞攻击
这些改动使 HashMap 在保持向后兼容的同时,性能、功能、安全性都得到了显著提升,体现了 Java 集合框架的持续演进。
更多推荐

所有评论(0)