HashTable、HashMap、ConcurrentHashMap 核心区别总结

HashMap、HashTable、ConcurrentHashMap 都是以哈希表为底层实现的 K-V 存储容器,面试高频对比点,核心差异集中在线程安全、null 值支持、扩容机制、锁粒度、性能、继承关系 。

1. 基础概念

  1. HashTable
    • JDK1.0 诞生,属于古老集合类
    • 线程安全,锁是对象锁(synchronized 修饰方法)
    • 底层:数组 + 链表(JDK1.8 没有升级红黑树)
  2. HashMap
    • JDK1.2 诞生,日常开发最常用
    • 非线程安全
    • JDK1.7:数组 + 链表;JDK1.8:数组 + 链表 / 红黑树(链表长度≥8 转红黑树,≤6 退化为链表)
  3. ConcurrentHashMap
    • JDK1.5 引入,并发场景替代 HashTable
    • 线程安全,分段锁(JDK1.7)/ CAS + synchronized 锁桶头节点(JDK1.8)
    • JDK1.7:Segment 分段数组 + HashEntry;JDK1.8:结构和 HashMap 保持一致(数组 + 链表 + 红黑树)

2. 逐项对比

2.1 线程安全

  • HashTable :线程安全 ,但是所有 put/get/remove 等方法都加 synchronized,锁整个哈希表对象。多个线程竞争同一把锁,并发下阻塞严重,性能差。
  • HashMap :线程不安全 ,无任何同步措施,多线程并发读写会出现数据丢失、死循环(JDK1.7 扩容链表头插法)、脏读,并发场景禁止直接使用。
  • ConcurrentHashMap :线程安全的哈希表
    • JDK1.7:Segment 分段锁,将整个哈希表分成多个段,不同段之间可以并发读写,同一 Segment 内互斥。
    • JDK1.8:效率更高,按照桶级别加锁,降低锁冲突概率 。(抛弃 Segment,采用 CAS + synchronized,只锁住当前要操作的桶(数组下标位置的头节点),锁粒度极小,并发性能远高于 HashTable。)

线程安全代码演示:

HashMap 在多线程并发 put 会发生数据丢失,HashTable、ConcurrentHashMap 保证数据安全。

java 复制代码
package org.Thread;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class MapThreadSafeTest {
    private static final int Count=10000;
    //接收任意一种 Map,开启两个线程往里面 put 数据,
    // 等两个线程全部跑完,打印最终 size。
    public static void testMap(Map<Integer,Integer> map) throws InterruptedException {
        Thread t1=new Thread(()->{
            for (int i = 0; i < Count; i++) {
                map.put(i,i);
            }
        });

        Thread t2=new Thread(()->{
            for (int i = Count; i < Count*2; i++) {
                map.put(i,i);
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        //等线程跑完之后,打印map里面的元素
        System.out.println(map.getClass().getSimpleName()+"size:"+map.size());
    }

    public static void main(String[] args) throws InterruptedException {
        //测试HashMap:非线程安全,大概率丢失数据,输出size<20000
        testMap(new HashMap<>());
        //测试Hashtable:线程安全,最终size一定等于20000
        testMap(new Hashtable<>());
        //测试ConcurrentHashMap:线程安全,最终size一定等于20000
        testMap(new ConcurrentHashMap<>());
    }

}

结果如下:

通过结果我们能知道 HashMap是线程不安全,而 Hashtable 和 ConcurrentHashMap是线程安全的

2.2 是否允许 null key /null value

  • HashTable :key 和 value 都不能为 null 。put 时会直接判断,空值抛出 NullPointerException。
  • HashMap :允许一个 null key,多个 null value。null key 固定放在下标 0 位置。
  • ConcurrentHashMap :key 和 value 都不允许 null 。原因:并发场景下无法区分 get(key) == null 是 key 不存在,还是存入的 value 本身就是 null,存在歧义。

2.3 继承与实现关系

  • HashTable:继承 Dictionary(古老抽象类),实现 Map
  • HashMap:继承 AbstractMap,实现 Map
  • ConcurrentHashMap:实现 ConcurrentMap(继承 Map)

Dictionary 是遗留类,现在基本废弃,推荐使用 AbstractMap。

2.4 初始容量 & 扩容规则

  • HashTable :默认初始容量 11 ;扩容公式:newCapacity = oldCapacity * 2 + 1,容量始终是奇数。
  • HashMap :默认初始容量 16 ;容量必须是 2 的幂;扩容:oldCapacity << 1(翻倍)。 容量为 2 的幂是为了 (n - 1) & hash 代替取模运算,提升哈希寻址效率。
  • ConcurrentHashMap
    • JDK1.7:Segment 默认 16 段,每个 Segment 初始容量 2;
    • JDK1.8:和 HashMap 一致,默认容量 16,2 的幂,扩容翻倍。

2.5 遍历方式 & 快速失败

  • HashTable、HashMap 的迭代器是 fail-fast(快速失败):遍历过程中其他线程修改结构(新增删除),抛出 ConcurrentModificationException。
  • ConcurrentHashMap 迭代器是 weakly consistent(弱一致性):遍历期间其他线程的修改不一定立刻感知,不会抛出并发修改异常,适合并发遍历。

2.6 使用场景

  • HashMap:单线程环境,优先使用;查询插入性能最好。多线程下需要外部手动加锁。
  • HashTable:基本淘汰。并发性能太差,新项目不使用。
  • ConcurrentHashMap:多线程并发读写场景,优先选择。

3. JDK1.8 重点变化小结(高频面试)

  1. HashMap:链表过长转为红黑树,优化长链表查询 O (n) -> O (logn)
  2. ConcurrentHashMap:移除 Segment 分段锁,改用 CAS + 锁桶头节点,锁粒度大幅降低,性能提升
  3. HashTable:没有改动,依旧是方法上加 synchronized,锁整张表

4. 总结

  • HashMap:单线程首选,无锁,支持 null,性能高,线程不安全。
  • HashTable:古董线程安全 Map,锁整张表,不支持 null,并发弱,基本废弃。
  • ConcurrentHashMap:并发场景首选,细粒度锁 / CAS,不支持 null,兼顾安全和并发性能。
相关推荐
郭小李1 小时前
spring mvc 工作流程
java·spring·mvc
SL_staff2 小时前
BI大屏嵌入MES的登录态难题:JVS统一账号体系实战解法
java·数据分析·数据可视化
乌暮2 小时前
综合案例:宠物电商系统
java·开发语言·spring·mvc·restful·1024程序员节
大文说跨境2 小时前
亚马逊多店铺的图片素材怎么批量去重:EXIF 与哈希的处理流程
开发语言·php
VimorphTech2 小时前
一段字幕太长,怎样拆句并安排显示时间?
java·spring boot
万联WANFLOW2 小时前
Kimi K3.1 Pro 接口接入指南:模型选型、费用计算与零数据保留
java·spring boot·python·安全·spring·大模型
织码weavecodes2 小时前
新闻资讯模块设计:内容发布、阅读计数与附件鉴权
java·服务器·数据库
学到老活到老2 小时前
Spring 事务怎样生效?为什么加了 @Transactional 仍不回滚?
java·spring
学到老活到老2 小时前
Spring 怎样创建 Bean?@Autowired 在哪一步生效?
java·spring