Java Map 三大核心实现类详解:HashMap、TreeMap、Hashtable

一、HashMap 深度解析(补充底层核心原理)

1. 存储结构:数组 + 链表 + 红黑树(JDK1.8 优化)

HashMap 的底层是「哈希桶数组」,每个桶对应一个链表 / 红黑树,核心结构如下:

  • 哈希桶数组 :默认初始容量 16(2 的幂次),数组下标 = key.hashCode() ^ (key.hashCode() >>> 16) & (capacity - 1)(扰动函数 + 与运算,减少哈希冲突);
  • 链表:当多个 key 哈希到同一个桶时,形成链表(JDK1.7 头插法,JDK1.8 尾插法,避免并发死循环);
  • 红黑树:当链表长度 ≥ 8 且数组容量 ≥ 64 时,链表转为红黑树(查询效率从 O (n) 优化为 O (log n));当链表长度 ≤ 6 时,红黑树转回链表。
2. 扩容机制(面试高频)
  • 触发条件 :元素个数 size > capacity × loadFactor(默认负载因子 0.75,即 16×0.75=12 时触发扩容);
  • 扩容规则:新容量 = 旧容量 × 2(保证是 2 的幂次),重新计算所有元素的哈希桶位置(rehash);
  • 性能优化 :创建 HashMap 时指定初始容量(如已知存储 1000 个元素,指定 new HashMap<>(1024)),避免频繁扩容。
3. 核心易错点
(1)key 为 null 的处理

HashMap 允许 key 为 null,底层会将 null 键的哈希值固定为 0,存入数组下标 0 的桶中:

java

运行

复制代码
HashMap<String, Integer> map = new HashMap<>();
map.put(null, 100);
System.out.println(map.get(null)); // 输出 100(正常访问)
(2)并发安全问题

HashMap 非线程安全,多线程环境下扩容可能导致死循环(JDK1.7)、数据丢失(JDK1.8),严禁在多线程中直接使用

  • 错误示例:多线程 put 元素可能导致 ConcurrentModificationException 或数据不一致;
  • 正确方案:使用 ConcurrentHashMap(分段锁 / CAS + synchronized,并发效率远高于 Hashtable)。
(3)自定义对象作为 key 的坑

自定义对象作为 key 时,必须重写 hashCode()equals(),否则会导致 key 重复:

java

运行

复制代码
// 反例:未重写hashCode/equals
class User {
    private String id;
    public User(String id) { this.id = id; }
}
HashMap<User, String> map = new HashMap<>();
map.put(new User("001"), "张三");
System.out.println(map.get(new User("001"))); // 输出 null(两个User对象哈希值不同)

// 正例:重写hashCode/equals
class User {
    private String id;
    public User(String id) { this.id = id; }
    
    @Override
    public int hashCode() {
        return id.hashCode(); // 基于唯一标识id计算哈希
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id);
    }
}
4. 遍历方式对比(实战推荐)

表格

遍历方式 优点 缺点 推荐度
entrySet 遍历 一次获取键值对,效率高 代码稍长 ★★★★★
keySet 遍历 仅遍历键,代码简洁 需二次 get (value),效率低 ★★★☆☆
forEach(Java8+) 代码极简 无法中途退出 ★★★★☆

java

运行

复制代码
HashMap<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);

// 推荐:entrySet遍历
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + " → " + entry.getValue());
}

// Java8+ 极简遍历
map.forEach((k, v) -> System.out.println(k + " → " + v));

二、TreeMap 深度解析(补充排序核心)

1. 红黑树的核心特性

TreeMap 基于红黑树(自平衡二叉搜索树) 实现,保证:

  • 左子树所有节点 <根节点,右子树所有节点> 根节点;
  • 任意节点的左右子树高度差 ≤ 1(平衡);
  • 增删改查效率稳定在 O (log n)(优于链表,劣于 HashMap)。
2. 排序的两种方式(补充细节)
(1)自然排序(Comparable)

自定义类作为 key 时,实现 Comparable 接口:

java

运行

复制代码
class Student implements Comparable<Student> {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    // 按分数降序排序
    @Override
    public int compareTo(Student o) {
        return o.score - this.score; // 降序;升序:this.score - o.score
    }

    @Override
    public String toString() {
        return name + "(" + score + ")";
    }
}

public class TreeMapDemo {
    public static void main(String[] args) {
        TreeMap<Student, String> map = new TreeMap<>();
        map.put(new Student("张三", 85), "一班");
        map.put(new Student("李四", 92), "二班");
        map.put(new Student("王五", 78), "三班");
        // 输出:李四(92)→二班, 张三(85)→一班, 王五(78)→三班
        map.forEach((k, v) -> System.out.println(k + "→" + v));
    }
}
(2)自定义排序(Comparator)

优先于自然排序,适合无法修改 key 类源码的场景:

java

运行

复制代码
// 按姓名长度排序(匿名内部类 → Lambda 简化)
TreeMap<Student, String> map = new TreeMap<>((s1, s2) -> {
    return s1.getName().length() - s2.getName().length();
});
3. 核心限制
  • key 不能为 null:红黑树排序时会调用 key.compareTo(),null 调用方法会抛 NullPointerException
  • 性能低于 HashMap:O (log n) 对比 O (1),仅在需要排序时使用。

三、Hashtable 深度解析(补充现代替代方案)

1. 核心缺陷(为何被淘汰)
  • 线程安全实现低效 :所有方法加 synchronized 关键字(对象级锁),即使多线程操作不同桶,也会互斥,并发性能极差;
  • 无红黑树优化:始终是数组 + 链表,哈希冲突严重时查询效率低;
  • 不支持 null 键值:put null 会直接抛异常;
  • 扩容规则不友好:新容量 = 旧容量 ×2 +1(非 2 的幂次),哈希分布不如 HashMap 均匀。
2. 现代替代方案(实战首选)

表格

场景 替代方案 核心优势
单线程 + 无需排序 HashMap 效率最高
单线程 + 需要排序 TreeMap 有序遍历
多线程 + 高并发 ConcurrentHashMap 分段锁 / CAS 实现,并发效率高
多线程 + 简单场景 Collections.synchronizedMap(new HashMap<>()) 快速适配,无需引入并发包
ConcurrentHashMap 示例(推荐)

java

运行

复制代码
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentMapDemo {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        
        // 多线程安全插入
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                map.put(Thread.currentThread().getName() + "-" + i, i);
            }
        };
        
        Thread t1 = new Thread(task, "线程1");
        Thread t2 = new Thread(task, "线程2");
        t1.start();
        t2.start();
        
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("最终元素个数:" + map.size()); // 2000(无数据丢失)
    }
}

四、三大 Map 类增强对比表

表格

特性 HashMap TreeMap Hashtable
底层结构 数组 + 链表 + 红黑树(JDK1.8+) 红黑树 数组 + 链表(无红黑树)
时间复杂度 增删改查 O (1)(平均) 增删改查 O (log n) 增删改查 O (1)(平均,同步开销大)
有序性 无序(按哈希桶分布) 有序(按键排序) 无序
线程安全 是(对象级锁,低效)
null 支持 key/value 均可为 null key 不可为 null,value 可为 null key/value 均不可为 null
初始容量 / 扩容 16 / 2 倍 无初始容量(红黑树动态增长) 11 / 2 倍 + 1
迭代器类型 Iterator(fail-fast) Iterator(fail-fast) Enumeration/Iterator
现代替代方案 -(单线程首选) -(有序场景首选) ConcurrentHashMap
适用场景 单线程、高效、无需排序 单线程、需要按键排序 仅老系统维护,新开发禁用

五、实战选型决策树(快速选对 Map)

复制代码
flowchart TD
    A[选择Map实现类] --> B{是否多线程环境?}
    B -->|是| C{高并发?}
    C -->|是| D[ConcurrentHashMap]
    C -->|否| E[Collections.synchronizedMap]
    B -->|否| F{是否需要按键排序?}
    F -->|是| G[TreeMap]
    F -->|否| H[HashMap]

六、开发最佳实践

  1. 优先用 HashMap:90% 的单线程场景首选,创建时指定初始容量优化性能;

  2. 自定义 key 必须重写 hashCode/equals:否则会导致 key 重复或查询不到;

  3. 多线程禁用 Hashtable:优先用 ConcurrentHashMap(JDK1.8+ 性能大幅优化);

  4. TreeMap 仅用于排序场景:如排行榜、字典序遍历,非排序场景用 HashMap;

  5. 避免遍历中修改 Map :遍历删除用 iterator.remove()map.removeIf(),避免 ConcurrentModificationException

  6. Java8+ 推荐用 Map.of () 创建不可变 Map :简化少量元素的初始化:

    java

    运行

    复制代码
    // 不可变Map(键值不可增删改)
    Map<String, Integer> immutableMap = Map.of("A", 1, "B", 2, "C", 3);

总结

  1. HashMap:基于哈希表,无序、高效、支持 null,单线程非排序场景首选,核心优化点是指定初始容量和重写 key 的 hashCode/equals;
  2. TreeMap:基于红黑树,有序、不支持 null key,仅适用于单线程按键排序的场景;
  3. Hashtable:古老的线程安全实现,效率极低,现代开发中已被 ConcurrentHashMap 完全替代;
  4. 选型核心:单线程看是否需要排序,多线程看并发强度(高并发用 ConcurrentHashMap)。
相关推荐
1104.北光c°2 小时前
双令牌机制:让认证更安全、体验更流畅
java·开发语言·笔记·后端·安全·token·双令牌
独自破碎E2 小时前
【面试真题拆解】Java文件操作的异常类型与受检_非受检异常
java·面试·职场和发展
q5431470872 小时前
Spring TransactionTemplate 深入解析与高级用法
java·数据库·spring
工一木子2 小时前
String.format 替换踩坑记:从遇坑、读源码到手写实现
java·jdk源码
江沉晚呤时2 小时前
RabbitMQ 延迟队列实战指南:C# 版订单超时与定时任务解决方案
开发语言·后端·ruby
源码师傅2 小时前
2026最新AI短剧创作系统源码 开发语言:PHP+MYSQL 无限SAAS 含图文搭建教程
开发语言·php·ai短剧创作系统源码·短剧创作系统·短剧创作源码
6+h2 小时前
【java IO】IO体系结构 + File类详解
java·数据库·php
海南java第二人2 小时前
Flink状态后端与容错机制深度剖析:TB级状态下的高可用实战
java·spring·flink