一、核心区别对比
| 对比项 | HashMap | TreeMap | HashTable |
|---|---|---|---|
| 底层实现 | 哈希表(数组+链表/红黑树) | 红黑树(平衡二叉搜索树) | 哈希表(类似HashMap) |
| 线程安全 | 非线程安全 | 非线程安全 | 线程安全(全表锁) |
| 键值顺序 | 无序 | 按键自然顺序或自定义排序 | 无序 |
| Null键值 | ✅ 允许null键和null值 | ❌ 键不能为null(值可null) | ❌ 键值均不能为null |
| 性能特点 | 查询极快(O(1)) | 查询较慢(O(log n)) | 性能差(锁开销) |
| 扩容机制 | 动态扩容(默认2倍) | 无需扩容(树自动平衡) | 动态扩容 |
| 推荐场景 | 高频查询、无需排序 | 需要排序或范围查询 | 遗留代码或特定兼容场景 |
二、适用场景
1. 用 HashMap
-
场景:
- 需要快速存取键值对,不关心顺序。
- 单线程环境或已自行处理线程安全。
- 允许键或值为
null。
-
示例:
Map<String, Integer> scores = new HashMap<>(); scores.put("Alice", 90); scores.put(null, 0); // 允许null键 int score = scores.get("Alice"); // 快速查询
2. 用 TreeMap
-
场景:
- 需要按键排序(如字典序、数值大小)。
- 需要范围查询(如找大于某值的键)。
- 键需自定义排序规则(传入
Comparator)。
-
示例:
// 按年龄降序排序 Map<String, Integer> ageMap = new TreeMap<>((a, b) -> b.compareTo(a)); ageMap.put("Bob", 25); ageMap.put("Alice", 30); // 输出顺序:Bob=25 → Alice=30(按键降序) // 范围查询:获取键在 "A" 到 "C" 之间的条目 SortedMap<String, Integer> subMap = ((TreeMap<String, Integer>)ageMap).subMap("A", "C");
3. 用 HashTable(已过时)
-
场景:
- 遗留系统或需要兼容老代码。
- 需要线程安全且无法升级到
ConcurrentHashMap。
-
注意:现代Java开发中,优先用
ConcurrentHashMap(分段锁,性能更好)。
三、性能取舍
-
HashMap vs TreeMap:
- HashMap:牺牲顺序换速度。
- TreeMap:牺牲速度换顺序。
-
HashTable vs ConcurrentHashMap:
- HashTable:全表锁,并发性能差。
- ConcurrentHashMap:分段锁,高并发下更优。
四、经典对比示例
1. 统计单词频率(无需排序)→ HashMap
Map<String, Integer> wordCount = new HashMap<>();
String text = "apple banana apple orange";
for (String word : text.split(" ")) {
wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
// 结果:{banana=1, orange=1, apple=2}(顺序随机)
2. 按学生ID排序显示 → TreeMap
Map<String, Integer> students = new TreeMap<>();
students.put("S002", 85);
students.put("S001", 90);
// 结果:{S001=90, S002=85}(按键升序)
3. 多线程环境 → ConcurrentHashMap(替代HashTable)
Map<String, String> safeMap = new ConcurrentHashMap<>();
safeMap.put("key", "value"); // 线程安全,分段锁
五、总结口诀
「HashMap 快如闪电,无序存取是首选
TreeMap 自动排,范围查询更方便
HashTable 已过时,并发推荐 Concurrent
按需选择三兄弟,场景匹配最关键!」