在 Java 中,哈希表(Hash Table)是一种基于哈希表数据结构实现的集合,主要用于存储键值对(Key-Value),其核心特点是查询、插入、删除操作的平均时间复杂度接近 O (1),是日常开发中非常常用的数据结构。
可以和双向链表配合实现LRU。
Java 中与哈希表相关的核心类主要有 HashMap、Hashtable 以及 ConcurrentHashMap(线程安全版本),其中 HashMap 是最常用的实现类。下面从哈希表的基本原理、Java 中的具体实现(以 HashMap 为例)、核心特性等方面详细介绍:
一、哈希表的基本原理
哈希表的核心思想是通过哈希函数将键(Key)映射到数组中的某个索引位置,从而实现快速访问。具体步骤如下:
- 哈希函数(Hash Function):对键进行计算,得到一个哈希值(整数),再通过取模运算(如
hash % 数组长度)确定该键在数组中的存储索引。 - 存储结构:哈希表的底层通常是一个数组(称为 “哈希桶数组”),每个数组元素(桶)可能存储一个键值对,或一个链表 / 红黑树的头节点(用于解决哈希冲突)。
- 哈希冲突(Hash Collision):当两个不同的键通过哈希函数计算得到相同的索引时,就会发生冲突。Java 中主要通过链地址法解决冲突:将冲突的键值对以链表形式存储在同一个桶中;当链表长度超过阈值(默认 8)时,会转为红黑树(JDK 1.8+),以提高查询效率。
二、Java 中 HashMap 的实现细节
HashMap 是 Java 中哈希表的典型实现,以下是其核心特性和实现逻辑:
1. 底层数据结构(JDK 1.8+)
- 由数组(Node [] table) + 链表 + 红黑树 组成。
- 数组的每个元素是
Node类型(实现Map.Entry接口),包含key、value、hash值和下一个节点的引用(next)。 - 当链表长度超过
TREEIFY_THRESHOLD(默认 8)且数组长度 ≥MIN_TREEIFY_CAPACITY(默认 64)时,链表会转为红黑树(TreeNode),以优化查询性能(红黑树查询时间复杂度为 O (log n),优于链表的 O (n))。
2. 哈希函数与索引计算
- 对键的
hashCode()进行二次哈希(扰动函数),减少哈希冲突:static final int hash(Object key) { int h; // 二次哈希:将hashCode的高16位与低16位异或,增加哈希值的随机性 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } - 计算索引:
(数组长度 - 1) & hash(等价于hash % 数组长度,但位运算效率更高)。
3. 扩容机制
- 数组的初始容量(
initialCapacity)默认是 16,负载因子(loadFactor)默认是 0.75。 - 当元素数量(
size)超过容量 × 负载因子时,会触发扩容(resize()):- 新容量为原容量的 2 倍(保证容量始终是 2 的幂,确保
(容量-1) & hash计算索引的均匀性)。 - 将原数组中的元素重新计算索引并迁移到新数组中(重新哈希)。
- 新容量为原容量的 2 倍(保证容量始终是 2 的幂,确保
4. 常用操作的流程
-
put(key, value):
- 计算 key 的哈希值和索引。
- 若索引位置为空,直接插入新 Node。
- 若索引位置不为空,判断是否为红黑树:
- 是:按红黑树规则插入。
- 否:遍历链表,若存在相同 key 则更新 value,否则插入链表尾部;插入后若链表长度达标则转为红黑树。
- 若插入后元素数量超过阈值,触发扩容。
-
get(key):
- 计算 key 的哈希值和索引。
- 检查索引位置的节点:
- 若为红黑树,按红黑树查找规则匹配 key。
- 若为链表,遍历链表匹配 key,返回对应的 value。
三、HashMap 与 Hashtable 的区别
| 特性 | HashMap | Hashtable |
|---|---|---|
| 线程安全 | 非线程安全(多线程操作需手动同步) | 线程安全(方法加 synchronized 锁) |
| 效率 | 效率高(无锁竞争) | 效率低(锁粒度大) |
| 允许的键值 | 允许 key 和 value 为 null(key 仅允许一个 null) | 不允许 key 或 value 为 null |
| 初始容量与扩容 | 初始容量 16,扩容为 2 倍 | 初始容量 11,扩容为 2 倍 + 1 |
| 迭代器类型 | 快速失败(fail-fast)迭代器 | 枚举器(Enumeration),不支持快速失败 |
四、哈希表的优缺点
- 优点:
- 查询、插入、删除的平均时间复杂度接近 O (1),效率极高。
- 支持通过键快速访问值,适合需要频繁查找的场景。
- 缺点:
- 哈希函数设计不佳可能导致大量冲突,退化为链表,性能下降。
- 元素存储无序(
HashMap不保证顺序,LinkedHashMap可保证插入顺序)。 - 扩容时需要重新哈希和迁移元素,可能产生性能开销。
五、使用场景
- 需要快速根据键查找值的场景(如缓存、字典)。
- 存储键值对且无需保证顺序的场景(如配置参数、用户信息映射)。
- 非线程安全场景优先用
HashMap,线程安全场景可用ConcurrentHashMap(比Hashtable更高效)。
2564

被折叠的 条评论
为什么被折叠?



