本文将从多个维度对比三者的核心差异,帮助开发者根据场景选择合适的数据结构:
1、 线程安全性
- HashTable:通过
synchronized
修饰所有方法实现线程安全,但高并发下性能差。
- HashMap/TreeMap:非线程安全,多线程需手动同步或使用并发容器。
特性 | HashMap | TreeMap | HashTable |
---|
线程安全 | 非线程安全 | 非线程安全 | 线程安全(方法全同步) |
替代方案 | 使用 ConcurrentHashMap | 使用 ConcurrentSkipListMap | 已过时,推荐 ConcurrentHashMap |
2、内部实现与数据结构
- HashMap:哈希表动态扩容,链表长度超阈值(默认8)转为红黑树。
- TreeMap:基于红黑树维护键的有序性,支持范围查询(如
subMap()
)。
- HashTable:传统哈希表,无红黑树优化,冲突处理仅用链表。
特性 | HashMap | TreeMap | HashTable |
---|
数据结构 | 数组 + 链表/红黑树(JDK8+) | 红黑树 | 数组 + 链表 |
冲突解决 | 拉链法 + 树化优化(JDK8+) | 自然无冲突(树结构) | 拉链法(无树化优化) |
有序性 | 无序 | 按键自然/比较器排序 | 无序 |
3、性能对比
- HashMap:哈希冲突少时性能最佳,冲突多时退化为链表或树。
- TreeMap:所有操作稳定在对数时间,适合需要排序的场景。
- HashTable:同步锁导致并发性能差,单线程下也不如 HashMap。
操作 | HashMap(平均) | TreeMap | HashTable |
---|
插入/删除 | O(1) | O(log n) | O(1) |
查询 | O(1) | O(log n) | O(1) |
遍历 | O(n) | O(n)(有序) | O(n) |
4、Null 键值支持
- TreeMap:插入 null 键会抛出
NullPointerException
(依赖自然排序或比较器)。
- HashTable:插入 null 键或值直接抛出异常。
特性 | HashMap | TreeMap | HashTable |
---|
Null 键 | ✅ 允许一个 null 键 | ❌ 不允许(需比较) | ❌ 不允许 |
Null 值 | ✅ 允许多个 null 值 | ✅ 允许(视比较器而定) | ❌ 不允许 |
5、 扩容机制
- HashMap:扩容时重新哈希分布键值,优化后减少计算(JDK8+)。
- HashTable:扩容策略较为保守,性能开销更大。
特性 | HashMap | HashTable | TreeMap |
---|
初始容量 | 16 | 11 | 无固定容量 |
扩容规则 | 2 倍增长(容量×2) | 2 倍+1(旧容量×2 +1) | 动态调整树结构 |
负载因子 | 0.75(默认) | 0.75(默认) | 不适用 |
6、使用场景建议
-
HashMap
- 快速键值查找,无需排序。
- 单线程或已手动处理同步的多线程环境。
- 示例:缓存数据、频率统计。
-
TreeMap
- 需要按键排序或范围查询。
- 示例:有序事件日志、按范围检索数据(如价格区间)。
-
HashTable
- 遗留代码兼容,现代开发优先使用
ConcurrentHashMap
。
7、总结对比表
维度 | HashMap | TreeMap | HashTable |
---|
线程安全 | ❌ | ❌ | ✅(同步方法) |
数据结构 | 哈希表+链表/红黑树 | 红黑树 | 哈希表+链表 |
有序性 | ❌ | ✅(按键排序) | ❌ |
Null 支持 | ✅键/值 | ❌键,✅值(视情况) | ❌键/值 |
性能 | O(1)(平均) | O(log n) | O(1)(但同步开销大) |
适用场景 | 高频查询、无需排序 | 排序需求、范围查询 | 遗留系统,线程安全需求 |
通过合理选择数据结构,可显著提升程序性能与代码可维护性。现代开发中,优先考虑 ConcurrentHashMap
(线程安全)和 TreeMap
(排序需求),避免使用过时的 HashTable
。
更多分享
- 一文带你吃透Android中常见的高效数据结构
- 详解:ArrayMap和SparseArray在HashMap上面的改进
- 详解:Set集合是如何保证元素不重复的
- 详解:LinkedHashMap的工作原理和实现
- 一文带你搞懂HashSet和TreeSet的区别