Map
HashMAP
类继承
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
...
}
- 同 ArrayList 和 LinkedList 一样也实现了 Serializable(支持序列化)、Cloneable(支持克隆)
- Map:定义了一些同一的行为
- AbstractMap:一些默认实现,避免重复编写功能
类成员
类属性
// 默认容量(数组长度),2^4 = 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量(数组长度),2^30 = 1,073,741,824
static final int MAXIMUM_CAPACITY = 1 << 30;
// 保存元素的数组(首次使用时才初始化)
transient Node<K,V>[] table;
// 元素数量,每次插入、删除时更新(不是数组长度,应该 <= 数组长度)
transient int size;
// 扩容阈值,当 size >= threshold 数组发生扩容
int threshold;
// 加载因子(用于计算扩容阈值,threshold = 数组长度 * loadFactor)
final float loadFactor;
// 默认的加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// Entry<K,V> 是每个元素,Set<Map.Entry<K,V>> 保存了所有的元素
transient Set<Map.Entry<K,V>> entrySet;
// 修改次数,同 size 每次插入、删除时更新
transient int modCount;
// 链表和红黑树的转换相关
// 1)链表转红黑树:链表长度 >= TREEIFY_THRESHOLD && 数组长度 >= MIN_TREEIFY_CAPACITY
// 2)红黑树退化为链表:扩容后只要红黑树的节点总数量 <= UNTREEIFY_THRESHOLD 就退化为链表(不管数组长度)
static final int TREEIFY_THRESHOLD = 8; // 链表长度 >= 8
static final int MIN_TREEIFY_CAPACITY = 64; // 数组长度 >= 64
static final int UNTREEIFY_THRESHOLD = 6; // 红黑树节点 <= 6
内部类
1. 数据结构类
| 内部类 | 作用 |
|---|---|
Node<K,V> |
基础键值对节点,用于存储链表结构的数据(hash, key, value, next)。 |
TreeNode<K,V> |
红黑树节点(继承自 Node),在桶链表过长时(≥8)转为红黑树优化查询。 |
2. 视图类(View Collections)
| 内部类 | 作用 |
|---|---|
KeySet |
返回所有键的集合(map.keySet()),支持遍历和删除。 |
Values |
返回所有值的集合(map.values()),仅支持遍历。 |
EntrySet |
返回所有键值对的集合(map.entrySet()),支持遍历和修改。 |
特点:
- 均继承自
AbstractSet或AbstractCollection,提供轻量级的数据视图。 - 操作(如
remove())会直接反映到底层HashMap。
3. 迭代器类(Iterators)
| 内部类 | 作用 |
|---|---|
HashIterator |
基础迭代器,提供 nextNode() 方法遍历所有节点(链表或树)。 |
KeyIterator |
键迭代器(继承 HashIterator),用于 keySet().iterator()。 |
ValueIterator |
值迭代器(继承 HashIterator),用于 values().iterator()。 |
EntryIterator |
键值对迭代器(继承 HashIterator),用于 entrySet().iterator()。 |
快速失败机制:
所有迭代器均通过检查 modCount 实现 ConcurrentModificationException。
4. 并行迭代器(Spliterators)
| 内部类 | 作用 |
|---|---|
HashMapSpliterator |
基础并行分割器,支持分片遍历(用于 Stream 并行操作)。 |
KeySpliterator |
键分割器(继承 HashMapSpliterator),用于 keySet().spliterator()。 |
ValueSpliterator |
值分割器(继承 HashMapSpliterator),用于 values().spliterator()。 |
EntrySpliterator |
键值对分割器(继承 HashMapSpliterator),用于 entrySet().spliterator()。 |
特点:
- 支持
trySplit()分割任务,优化多线程并行处理。 - 用于
Stream API的并行流(如map.keySet().parallelStream())。
5. 工具类(Utilities)
| 内部类 | 作用 |
|---|---|
UnsafeHolder |
(JDK 内部使用)通过 Unsafe 操作实现低级别内存访问优化。 |
构造方法
无参构造:不会立即创建数组
public HashMap() {
// 0.75,初始化加载因子为默认的值,其他属性也都是默认值,不会创建数组(添加元素时创建)
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
指定容量:不会立即创建数组
会根据传入的容量调整为 >= initialCapacity 的最小 2 的幂
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR); // 会自动计算合适的容量
}
指定容量和加载因子:不会立即创建数组
public HashMap(int initialCapacity, float loadFactor) {
// 保证传入的 initialCapacity 不能超过最大值,也不能小于 0
...
this.loadFactor = loadFactor;
// 内部使用 >>>(使用无符号右移运算,目的是算出 >= initialCapacity 的 2 的幂的值出来)
// 虽然这里赋值给扩容阈值 threshold,但是后面在扩容时以这个值作为数组的长度(其实这里是计算数组的初始长度),然后根据数组长度和加载因子重新算 threshold
this.threshold = tableSizeFor(initialCapacity);
}
// 因为 HashMap 的数组是懒加载方式创建的,所以会在扩容时真正初始化
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold; // 构造方法中暂存的初始容量(2的幂)
int newCap, newThr = 0;
if (oldCap == 0) { // 首次初始化
newCap = oldThr; // 使用构造方法计算的容量(2的幂)
newThr = (int)(newCap * loadFactor); // 计算真正的扩容阈值
}
// ...后续扩容逻辑
threshold = newThr; // 更新阈值
return new Node[newCap]; // 创建新数组
}
指定一个 map:会创建数组
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR; // 默认的加载因子
putMapEntries(m, false);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // 未初始化
float ft = ((float)s / loadFactor) + 1.0F; // 计算数组所需的最小容量
int t = (ft > MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : (int)ft;
if (t > threshold)
threshold = tableSizeFor(t); // 保证容量是 2 的幂
}
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
putVal(hash(e.getKey()), e.getKey(), e.getValue(), false, evict);
}
}
}
float ft = ((float)s / loadFactor) + 1.0F
如果 s = 6,loadFactor = 0.75,则 6 / 0.75 = 8
表示哈希表至少需要容量 8,才能在不扩容的情况下容纳 6 个元素(8 * 0.75 = 6)
+1 的原因:添加一个缓冲值,避免浮点计算误差导致容量不足,例如:若 s / loadFactor = 7.999,直接取整会得到 7,但实际需要 8
添加元素
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; // 定义几个变量
// 如果数组为空或长度是0就扩容(同时 n 和 tab 已经赋值了:n 是 数组长度,tab 是数组)
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// n-1:是数组最后一个下标(n 是长度)
// & 运算:是当超出数组时把元素放在哪一格(拉链法,前提必须数组长度是2的幂才可以使用 & 运算)
// 也给 p 赋值了,就是新元素应该插入的位置的元素(可能这里已经有元素了,也可能没有元素)
if ((p = tab[i = (n - 1) & hash]) == null) // 如果为空,就说明这个位置没有元素,直接插入新元素
tab[i] = newNode(hash, key, value, null);
else { // 如果数组这个位置已经有元素了
Node<K,V> e; K k;
// hash 相同,equals 也相同,覆盖元素(修改元素)
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 不是修改元素,此时位置也有元素,两种可能:1 当前元素是链表;2:当前元素是红黑树
else if (p instanceof TreeNode) // 如果是数节点,按照红黑树的方式插入元素
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 如果是链表,链表的方式插入元素,这个 for 循环就是在遍历链表
for (int binCount = 0; ; ++binCount) { // binCount 会是链表的索引(索引不是很合适,当前第几个元素)
// 是否达到链表尾部(尾插法)
if ((e = p.next) == null) { // p 是当前下标的元素(此时是链表)
p.next = newNode(hash, key, value, null); // 新元素入队(尾插法)
if (binCount >= TREEIFY_THRESHOLD - 1) // 当前 binCount 是否 >= 7,加上的新元素就是 8
treeifyBin(tab, hash); // 链表元素数量已经达到 8 了,方法里面会再次判断如果数组长度达到 64 就会转为红黑树
break;
}
// 如果不是链表尾部,检查 e 的 key 和新元素的 key 是否相同
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break; // 如果相同,退出循坏(后续会覆盖值)
p = e; // 如果不同,继续遍历链表(继续循环)
}
}
if (e != null) { // e 有值,新元素和链表中已存在的元素 key 相同,覆盖值
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount; // 修改次数+1
if (++size > threshold) // 是否需要扩容
resize();
afterNodeInsertion(evict);
return null;
}
扩容
final Node<K,V>[] resize() {
// 1. 保存旧哈希表
Node<K,V>[] oldTab = table;
// 旧容量:如果表为空则为0,否则取当前长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 旧扩容阈值
int oldThr = threshold;
int newCap, newThr = 0; // 新的容量扩容阈值
// -------------------------------
// 2. 计算新容量和阈值(分4种情况)
// -------------------------------
// 情况1:旧容量 > 0(说明已经初始化过)
if (oldCap > 0) {
// 如果旧容量已经达到最大值(2^30),不再扩容
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 新容量 = 旧容量 * 2
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
// 新阈值 = 旧阈值 * 2
newThr = oldThr << 1;
}
// 情况2:旧容量=0但旧阈值>0(发生在带初始容量的构造方法)
else if (oldThr > 0)
// 新容量 = 旧阈值(构造方法中暂存的初始容量)
newCap = oldThr;
// 情况3:旧容量=0且旧阈值=0(默认无参构造)
else {
// 新容量 = 默认初始容量(16)
newCap = DEFAULT_INITIAL_CAPACITY;
// 新阈值 = 默认负载因子(0.75)* 默认容量(16)= 12
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 情况4:如果新阈值未计算(如情况2未设置newThr)
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr; // 更新全局阈值
// -------------------------------
// 3. 创建新哈希表并迁移数据
// -------------------------------
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
// 遍历旧表的每个桶
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null; // 清空旧桶(帮助GC)
// 情况1:桶中只有一个节点(直接重新哈希)
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 情况2:桶中是红黑树(拆分树)
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
// 情况3:桶中是链表(拆分为高低位链表,原来的链表最多拆出两个链表,使用高低位异或运算效率更高)
else {
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
// 判断节点属于低位还是高位
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 低位链表放在原索引位置
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// 高位链表放在新索引位置(原索引 + oldCap)
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
链表转红黑树
有个前提走到这里链表元素已经达到 8 了,这个方法是决定是否要将链表转为红黑树
final void treeifyBin(Node<K,V>[] tab, int hash) {
// n: 数组长度,index: 目标桶索引,e: 当前链表节点
int n, index; Node<K,V> e;
// 数组为空或数组长度小于 64,不会转为红黑树,直接扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) { // 数组长度 >= 64,转红黑树
TreeNode<K,V> hd = null, tl = null;
// 遍历链表,这个循环时把链表的每个节点转成树节点
do {
TreeNode<K,V> p = replacementTreeNode(e, null); // 链表的 node 转 树节点
if (tl == null) // 维护树节点的链表关系
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab); // 树节点的链表准备好了,真正开始树化。后续逻辑后续再深入吧(红黑树目前不是特别了解~)
}
}
红黑树退化为链表
- java.util.HashMap#remove(java.lang.Object)
- java.util.HashMap#removeNode
- java.util.HashMap.TreeNode#removeTreeNode
- java.util.HashMap.TreeNode#untreeify
总结
- 基于哈希表实现(数组+链表/红黑树),如果是链表采用尾插法
- 元素无序,不允许重复,键值都可以为 null,只能一个 null 键,线程不安全
- 默认初始容量16(必须2的幂),最大容量 2^30,默认加载因子 0.75
- 链表转红黑树:数组长度 >= 64 && 链表长度 >= 8,查询从 O(n) 优化为 O(log n)
- 红黑树退化为链表:树节点 <= 6(不关心数组长度)
size > threshold(threshold = capacity * loadFactor)时触发扩容,扩容操作如下:- 新容量 = 旧容量 × 2(保持 2 的幂)
- 重新哈希(高效迁移:单节点直接定位,链表拆分为高低位链表)
- 作为 key 的对象需要重写 hashCode 和 equals 方法
- 遍历时
entrySet()比keySet() + get()效率更高(减少哈希计算)
其他 MAP
| 特性 | HashMap |
Hashtable |
LinkedHashMap |
TreeMap |
|---|---|---|---|---|
| 线程安全 | 否 | 是(全表锁) | 否 | 否 |
| 允许 null 键值 | 是 | 否 | 是 | 仅值可为 null |
| 顺序 | 无序 | 无序 | 插入顺序/访问顺序 | 按键排序 |
| 底层结构 | 哈希表 | 哈希表 | 哈希表 + 双向链表 | 红黑树 |
- HashTable 和 HashMap 的区别基本就是 HashTable 的方法都加了 synchronized 来保证线程安全
- TreeMap 和 HashMap 区别就是底层结构不一样,TreeMap 纯粹用红黑树实现
- 既然用红黑树,key 就不能为空
- 既然用红黑树,就一定是有顺序的(红黑树是近似平衡的二叉搜索树,都是搜索树了一定要有顺序)
- 既然所有的 key 都要用顺序,key 就不能是 null 了
插入数据时,元素所在的数组下标是根据 hash 值确定的,所以就不会有顺序(数组某个元素是有顺序的,因为红黑树和链表本来就有顺序)
LinkedHashMap 继承自 HashMap,为什么 LinkedHashMap 会有是有顺序的?
因为 LinkedHashMap 扩展了 HashMap 的 Entry
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; // 双向链表指针
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}

浙公网安备 33010602011771号