前言

在上一篇博客中,我们理清了集合框架的家族关系。但在面试中,面试官往往会针对 HashMap 的内部细节进行“连环炮”式发问。接下来带大家拆解这些硬核知识点,让你在面试中不仅能答对,还能答出深度。


一、 HashMap 的核心秘密:哈希碰撞与扩容

1. 为什么 HashMap 的长度要是 2 的幂次方?

这是为了性能优化。 计算元素在数组中位置的公式是 (n - 1) & hash(n 为数组长度)。

  • 原因 1: 当 n 为 2 的幂次方时,(n - 1) 的二进制全为 1(如 16-1=15,二进制为 1111)。进行 & 位运算时,效果等同于取模(%),但位运算的速度远快于取模。

  • 原因 2: 能使数据均匀分布,减少哈希冲突。

2. 负载因子(Load Factor)为什么是 0.75?

  • 权衡的结果:如果负载因子太小(如 0.5),数组空位多,浪费空间;如果太大(如 1.0),冲突概率激增,查询效率下降。

  • 0.75 是在时间和空间成本上的一个绝佳折中。


二、 线程安全集合:从被淘汰到主流

1. 被遗忘的古董:Hashtable 与 Vector

它们通过给每个方法加 synchronized 关键字来实现线程安全。

  • 缺点:锁的粒度太粗。只要有一个人在修改,其他人连读都不行。就像全班共用一支笔,效率极低,现在已经基本被弃用

2. 临时救兵:Collections.synchronizedXxx

可以通过 Collections.synchronizedMap(new HashMap<>()) 包装一个线程安全的集合。其本质还是加了全局锁,性能提升有限。

3. 当红炸子鸡:ConcurrentHashMap

它是多线程环境下的首选。

  • JDK 1.7:采用 分段锁(Segment),把大数组切成 16 段,每段一把锁。

  • JDK 1.8:抛弃分段锁,直接采用 CAS + synchronized。锁的粒度细化到了每一个数组桶(Node),只要两个线程操作的不是同一个位置,就不会产生竞争。


三、 CopyOnWriteArrayList:读写分离的智慧

当你需要在多线程环境下使用 ArrayList,且读多写少时,推荐使用 CopyOnWriteArrayList

  • 原理:写操作时,不直接修改原数组,而是把旧数组复制出一份,在副本上修改,改完后再将原引用指向新数组。

  • 优点:读取时完全不用加锁,性能极高。

  • 缺点:内存占用双倍,且数据具有“弱一致性”(改完的一瞬间,读取的人可能还在看旧副本)。

Java

// 写操作源码核心思想
public boolean add(E e) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        es = Arrays.copyOf(es, len + 1); // 复制一个副本
        es[len] = e;                     // 修改副本
        setArray(es);                   // 引用切向新副本
        return true;
    }
}

四、 常见面试连环炮(避坑指南)

Q1:HashMap 和 HashSet 的区别?

  • HashMap 实现了 Map 接口,存储键值对;HashSet 实现了 Set 接口,仅存储对象。

  • HashSet 的底层其实就是 HashMap,它把存进去的值当作 HashMap 的 Key。

Q2:ConcurrentHashMap 的 get 方法需要加锁吗?

不需要。 它的 Node 节点的 valnext 都用了 volatile 修饰,保证了可见性。这使得读操作性能极佳。

Q3:为什么 ConcurrentHashMap 不允许插入 null?

为了避免二义性。在并发环境下,如果 get(key) 返回 null,你无法分辨是“这个 Key 对应的值就是 null”,还是“这个 Key 根本不存在”。因为在判断的间隙,其他线程可能已经修改了数据。


五、 总结与建议

作为后端开发,如果你在处理高并发业务,请记住以下选择策略:

  1. 单线程或局部变量:永远首选 HashMap / ArrayList

  2. 多线程环境

    • 追求高性能 KV 存储 -> ConcurrentHashMap

    • 读多写少且数据量小 -> CopyOnWriteArrayList

  3. 注意点:集合操作时,尽量预估大小,减少扩容带来的 CPU 抖动。


结语: 源码虽然枯燥,但它是通往高级工程师的必经之路。理解了底层,你写的每一行代码都会变得更有底气。

如果这篇文章对你有启发,欢迎转发给一起备战的小伙伴!

Logo

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

更多推荐