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 集合框架的持续演进。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐