简介
HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对
类
-
public
class HashMap<K,V> extends AbstractMap<K,V>
-
implements
Map<
K,
V>,
Cloneable,
Serializable
属性
-
// 默认数组长度 16
-
static
final
int DEFAULT_INITIAL_CAPACITY =
1 <<
4;
// aka 16
-
// 最大数组长度
-
static
final
int MAXIMUM_CAPACITY =
1 <<
30;
-
// 默认加载因子
-
static
final
float DEFAULT_LOAD_FACTOR =
0.75f;
-
// 链表转红黑树阈值
-
static
final
int TREEIFY_THRESHOLD =
8;
-
// 红黑树转链表阈值
-
static
final
int UNTREEIFY_THRESHOLD =
6;
-
// 链表转红黑树限制
-
static
final
int MIN_TREEIFY_CAPACITY =
64;
-
// 元素数组
-
transient Node<K,V>[] table;
-
// 将数据转换成set的另一种存储形式,这个变量主要用于迭代功能
-
transient Set<Entry<K,V>> entrySet;
-
// 数组长度
-
transient
int size;
-
// 修改次数
-
transient
int modCount;
-
// 扩容阈值
-
int threshold;
-
// 实际加载因子
-
final
float loadFactor;
基础方法
-
public int size() {
-
return size;
-
}
-
public boolean isEmpty() {
-
return size ==
0;
-
}
-
public boolean containsKey(Object key) {
-
return getNode(hash(key), key) !=
null;
-
}
获取Hash
-
static final int hash(Object key) {
-
int h;
-
return (key ==
null) ?
0 : (h = key.hashCode()) ^ (h >>>
16);
-
}
从源码中可以看出,hash算法实际上就键的hashCode与hashCode无符号右移16位异或,为什么要搞这么麻烦呢?hashCode范围-2147483648到2147483648,这么大的数HashMap根本放不下,那么他是如何确定元素所在的数组下标呢?没错就是通过余数,正常情况下我们取余通过%,例如
-
9%2=
1
-
9%4=
1
在特殊情况下针对这种除数是2的n次幂还可以用&,例如
-
1001&
0001=
1(
9%2或者
9&(
2-
1))
-
1001&
0011=
1(
9%4或者
9&(
4-
1))
二进制中,一个数右移1位相当于除以2的商,而恰巧被移除出去的那一位就是除以2得到的余数,例如
9>>1 二进制 1001>>1=100,结果为4,9除以2等于4,1001向右移1位被移除的是1,9模2等于1
不仅是除以2,对于一个数要除以2的n次幂,也就是相当于把这个数向右移n位,而被移出去的n位即正好是我们要求是余数。例如
-
9
>>
2 二进制
1001>>
2=
10,结果为
2
-
9除以
4(
2的
2次幂)等于
2,
1001向右移
2位被移除的是
1,
9模
4等于
1
-
19
>>
3 二进制
10011>>
3=
10,结果为
2
-
19除以
8(
2的
3次幂)等于
2,
10011向右移
3位被移除的是
3,
19模
8等于
3
对于除数是2的n次方的算式,我们只需要得到被除数的低n位就可以了,而正好,对于2的n次方这样的数,我们将其转换为二进制之后,它就是第n+1位为1,其余低位都为0的数,因此我们将其减1,就得到了第n+1位为0,而其他位都为1的数,用此数与被除数k进行位与运算,就得到了被除数的低n位二进制数
若一个数m满足:m=2n,那么k % m = k & (m-1)
通过上面可以的值hashCode&(16-1)可以得到余数,把前面换成hashCode&1111可以看出,实际上只有hashCode后4位参与运算,前面都是无效数据(都被0替换)。这样算出来的散列效果并不好,有没有办法把前面也参与运算?于是就有了高位与地位先异或一次,让结果中包含高位特征,然后在取余。
计算长度
-
static final int tableSizeFor(int cap) {
-
int n = cap -
1;
-
n |= n >>>
1;
-
n |= n >>>
2;
-
n |= n >>>
4;
-
n |= n >>>
8;
-
n |= n >>>
16;
-
return (n <
0) ?
1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n +
1;
-
}
此方法返回一个比给定整数大且最接近的2的幂次方整数,先排除cap - 1以9为例
-
9
>>>
1 二进制
1001>>>
1=
100即
4,
9
|4二进制1001 |
100=
1101即
13
-
13
>>>
2 二进制
1101>>>
2=
11即
3,
13
|3二进制1101 |
11=
1111即
15
-
15
>>>
4 二进制
1111>>>
4=
0即
3,
15
|0二进制1111 |
0=
1111即
15
-
15
>>>
8 二进制
1111>>>
8=
0即
3,
15
|0二进制1111 |
0=
1111即
15
-
15
>>>
16 二进制
1111>>>
16=
0即
3,
15
|0二进制1111 |
0=
1111即
15
最后n+1即16,这里你发现了什么?9的二进制1001最后全变成了1111,实际上就是把这个数所有为都变成1,最后在加1一个是2的n次幂(2的n次幂,转换二进制就是n+1位为1其他位都是0,2的n次幂减一,转换二进制就是从n位开始都是1)。如果cap为2的n次幂时,n+1位都会变成1,这样都超过我们想要的值了,所以要cap-1。(负数补码最终都会变成-1)
构造函数
-
public HashMap(int initialCapacity, float loadFactor) {
-
// 判断长度是否越界
-
if (initialCapacity <
0)
-
throw
new IllegalArgumentException(
"Illegal initial capacity: " +
-
initialCapacity);
-
if (initialCapacity > MAXIMUM_CAPACITY)
-
initialCapacity = MAXIMUM_CAPACITY;
-
// 判断加载因子不能小于等于0,也不能是无穷值NAN
-
if (loadFactor <=
0 || Float.isNaN(loadFactor))
-
throw
new IllegalArgumentException(
"Illegal load factor: " +
-
loadFactor);
-
// 设置加载因子
-
this.loadFactor = loadFactor;
-
// 计算扩容阈值(初始化时阈值并没有乘以加载因子)
-
this.threshold = tableSizeFor(initialCapacity);
-
}
此构造函数只设置了加载因子和计算下次扩容阈值,并没有创建数组
-
public HashMap(int initialCapacity) {
-
// 调用两个参数的构造函数,加载因子使用默认值
-
this(initialCapacity, DEFAULT_LOAD_FACTOR);
-
}
此构造函数调用上面构造函数初始化
-
public HashMap() {
-
// 设置默认加载因子
-
this.loadFactor = DEFAULT_LOAD_FACTOR;
// all other fields defaulted
-
}
最常用的构造函数,除了设置默认加载因子,其实什么事也没干
-
public HashMap(Map<? extends K, ? extends V> m) {
-
// 设置默认加载因子
-
this.loadFactor = DEFAULT_LOAD_FACTOR;
-
putMapEntries(m,
false);
-
}
调用putMapEntries方法,方法如下
-
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
-
// 获取m长度
-
int s = m.size();
-
// 判断长度大于0
-
if (s >
0) {
-
// 当前table为空
-
if (table ==
null) {
-
// 长度除以加载因子(5/0.75=6.666...),算长度时只能算整数所以加1
-
float ft = ((
float)s / loadFactor) +
1.0F;
-
int t = ((ft < (
float)MAXIMUM_CAPACITY) ?
-
(
int)ft : MAXIMUM_CAPACITY);
-
// 以m算出来的数组长度大于扩容阈值,则修改扩容阈值
-
// 当前table为空,所以会用这个阈值当数组初始化长度
-
if (t > threshold)
-
threshold = tableSizeFor(t);
-
}
-
else
if (s > threshold)
-
// 长度大于扩容阈值,先扩容一次
-
resize();
-
// 遍历参数m的所有entry
-
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
-
K key = e.getKey();
-
V value = e.getValue();
-
// 向当前map中添加元素
-
putVal(hash(key), key, value,
false, evict);
-
}
-
}
-
}
键取值
-
public V get(Object key) {
-
// 声明节点
-
Node<K,V> e;
-
// 调用getNode获取节点,不为空时取值
-
return (e = getNode(hash(key), key)) ==
null ?
null : e.value;
-
}
-
final Node<K,V> getNode(int hash, Object key) {
-
// 声明变量
-
Node<K,V>[] tab; Node<K,V> first, e;
int n; K k;
-
// 判断数组是否为空,判断元素个数是否等于0,判断数组中头元素是否为空
-
if ((tab = table) !=
null && (n = tab.length) >
0 &&
-
(first = tab[(n -
1) & hash]) !=
null) {
-
// 判断头元素hash是否一样,一样时判断键是否一样
-
if (first.hash == hash &&
-
((k = first.key) == key || (key !=
null && key.equals(k))))
-
// 都一样返回头元素
-
return first;
-
// 不一样找下级元素
-
if ((e = first.next) !=
null) {
-
// 头元素为树节点去红黑树中找
-
if (first
instanceof TreeNode)
-
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
-
// 头节点不为树节点,就是链表遍历链表查找
-
do {
-
// hash一样,键一样就返回当前元素
-
if (e.hash == hash &&
-
((k = e.key) == key || (key !=
null && key.equals(k))))
-
return e;
-
// 给e赋,为空时退出循环
-
}
while ((e = e.next) !=
null);
-
}
-
}
-
// 没找到返回null
-
return
null;
-
}
添加方法
-
public V put(K key, V value) {
-
// 调用putVal方法
-
return putVal(hash(key), key, value,
false,
true);
-
}
-
// onlyIfAbsent 表示是否仅在 oldValue 为 null 的情况下更新键值对的值
-
// evict 如果为false,则表处于创建阶段
-
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
-
if ((tab = table) ==
null || (n = tab.length) ==
0)
-
// 开始初始化(使用下次扩容阈值扩容并返回长度)
-
n = (tab = resize()).length;
-
-
// 通过取余得到索引,通过索引找到槽,判断槽中是否有元素
-
if ((p = tab[i = (n -
1) & hash]) ==
null)
-
// 没有元素时,构建元素并存入
-
tab[i] = newNode(hash, key, value,
null);
-
else {
// 槽中有元素的逻辑(p不为空)
-
// 声明临时变量
-
Node<K,V> e; K k;
-
// 如果键跟头元素一样,则将e指向该键值对
-
if (p.hash == hash &&
-
((k = p.key) == key || (key !=
null && key.equals(k))))
-
// 一样赋值给e
-
e = p;
-
// 如果p是树节点,即走红黑树插入逻辑
-
else
if (p
instanceof TreeNode)
-
e = ((TreeNode<K,V>)p).putTreeVal(
this, tab, hash, key, value);
-
else {
-
// 最后走链表逻辑
-
for (
int binCount =
0; ; ++binCount) {
-
// 直到找到尾部
-
if ((e = p.next) ==
null) {
-
// 能进入到这儿,e一定为空,表示没有重复建
-
p.next = newNode(hash, key, value,
null);
-
// binCount从0开始,大于等于7就会尝试链变树
-
// 判断前已经添加,所以binCount=7时实际元素是8
-
// 前面又加了一个,所以从第9个开始尝试变树
-
if (binCount >= TREEIFY_THRESHOLD -
1)
-
// 尝试变树
-
treeifyBin(tab, hash);
-
break;
-
}
-
// 如果键跟e一样,则跳出循环
-
if (e.hash == hash &&
-
((k = e.key) == key || (key !=
null && key.equals(k))))
-
break;
-
// 当前轮e赋值给p
-
p = e;
-
}
-
}
-
// 当e不为空,也就是有重复值
-
if (e !=
null) {
-
V oldValue = e.value;
-
// 原值为空时,无论onlyIfAbsent是什么,都会被新值覆盖
-
if (!onlyIfAbsent || oldValue ==
null)
-
e.value = value;
-
// 此方法在HashMap中是空方法,LinkedHashMap中会讲
-
afterNodeAccess(e);
-
// 返回原值
-
return oldValue;
-
}
-
}
-
// 能走到这儿,至少说明没有重复建(有重复前面已经返回了)
-
// 并且添加新元素成功,修改次数加1
-
++modCount;
-
// size加1,并判断是否达到下次扩容阈值
-
if (++size > threshold)
-
// 扩容
-
resize();
-
// 此方法在HashMap中是空方法,LinkedHashMap中会讲
-
afterNodeInsertion(evict);
-
// 添加成功返回空
-
return
null;
-
}
从putVal源代码中我们可以知道,当插入一个元素成功的时候size就加1,若size大于threshold的时候,就会进行扩容(1、替换元素不会触发扩容,2、先添加元素在判断扩容)。按数组默认长16,扩容法值12,当我们加完第13个元素后开始扩容,若果中间有hash冲突可能只用了10个槽,一样会扩容,HashMap是否会空槽,跟hash算法有关。扩容会遍历所有的元素,时间复杂度很高,但是扩容处理后,元素会更加均匀的分布在各个槽中,会提升访问效率。
链表转树前置方法
-
final void treeifyBin(Node<K,V>[] tab, int hash) {
-
int n, index; Node<K,V> e;
-
// 只有在添加时,链表长度超过8才会调用这个方法
-
// 数组为空或者,数组长度小于64,不管有没有达到扩容阈值都会扩容一次
-
if (tab ==
null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
-
resize();
-
// 槽中链表头元素不为空
-
else
if ((e = tab[index = (n -
1) & hash]) !=
null) {
-
TreeNode<K,V> hd =
null, tl =
null;
-
do {
-
// 普通节点转为树节点
-
TreeNode<K,V> p = replacementTreeNode(e,
null);
-
// 当前轮临时元素为空(第一次)
-
if (tl ==
null)
-
// 设置头元素
-
hd = p;
-
else {
-
// 设置新节点前面节点为当前轮tl
-
p.prev = tl;
-
// 当前轮临时结点下一个结点设置为新节点
-
tl.next = p;
-
}
-
// 节点下移
-
tl = p;
-
}
while ((e = e.next) !=
null);
// 原链表中节点末尾退出
-
// 替换槽中整条链,当新双向链表不为空时(节点已经替换成树节点)
-
if ((tab[index] = hd) !=
null)
-
// 双向链表转红黑树
-
hd.treeify(tab);
-
}
-
}
扩容
-
final Node<K,V>[] resize() {
-
// 原数组
-
Node<K,V>[] oldTab = table;
-
// 原数组长度
-
int oldCap = (oldTab ==
null) ?
0 : oldTab.length;
-
// 原扩容阈值
-
int oldThr = threshold;
-
// 新长度,新扩容阈值
-
int newCap, newThr =
0;
-
// 原数组长度大于0
-
if (oldCap >
0) {
-
// 是否达到上限
-
if (oldCap >= MAXIMUM_CAPACITY) {
-
// 达到上限不在扩容
-
threshold = Integer.MAX_VALUE;
-
return oldTab;
-
}
-
// 原长度扩容一倍,并且新长度小于上限,原长度大于16
-
else
if ((newCap = oldCap <<
1) < MAXIMUM_CAPACITY &&
-
oldCap >= DEFAULT_INITIAL_CAPACITY)
-
// 新扩容阈值也增加一倍
-
newThr = oldThr <<
1;
// double threshold
-
}
-
// 原扩容阈值大于0
-
else
if (oldThr >
0)
-
// 原数组长度为0,原扩容阈值大于0,只有在初始化时存在
-
newCap = oldThr;
-
else {
-
// HashMap新建状态赋值
-
newCap = DEFAULT_INITIAL_CAPACITY;
-
newThr = (
int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
-
}
-
-
// 新扩容阈值为0(只有在初始化时,走到这儿才会新扩容阈值等于0)
-
if (newThr ==
0) {
-
// 计算新扩容阈值(新长度*加载因子,并且小于上限)
-
float ft = (
float)newCap * loadFactor;
-
newThr = (newCap < MAXIMUM_CAPACITY && ft < (
float)MAXIMUM_CAPACITY ?
-
(
int)ft : Integer.MAX_VALUE);
-
}
-
// 赋值新扩容阈值
-
threshold = newThr;
-
// 创建新数组
-
@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;
-
// e这时为槽里第一个元素(无论是链表还是红黑树,找到头元素,下面的都可以获得)
-
if ((e = oldTab[j]) !=
null) {
-
// 头元素拿出来后,当前槽清空
-
oldTab[j] =
null;
-
// 只有一个元素
-
if (e.next ==
null)
-
// 只有一个元素时,直接放入新数组槽中
-
newTab[e.hash & (newCap -
1)] = e;
-
// 红黑树
-
else
if (e
instanceof TreeNode)
-
// 请看TreeNode的split方法
-
((TreeNode<K,V>)e).split(
this, newTab, j, oldCap);
-
// 链表
-
else {
-
// loHead 原槽中头节点,loTail 原链当前节点
-
Node<K,V> loHead =
null, loTail =
null;
-
// hiHead 扩容槽中头节点,hiTail 扩容链当前节点
-
Node<K,V> hiHead =
null, hiTail =
null;
-
// 下级元素
-
Node<K,V> next;
-
// 遍历链表上所有元素
-
do {
-
// 先取出当前元素的下级元素
-
next = e.next;
-
// 判断是否可以放在原槽中(0可以,oldCap需要移动)
-
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;
-
}
-
if (hiTail !=
null) {
-
// 这一步主要作用是清空原结构冗余链
-
hiTail.next =
null;
-
// 整个扩容头结点放入扩容后槽中
-
newTab[j + oldCap] = hiHead;
-
}
-
}
-
}
-
}
-
}
-
// 返回新数组
-
return newTab;
-
}
很多人对(e.hash & oldCap) == 0可能会有疑问,这里解释一下。它的结果只能是0或者oldCap,认真看来hash获取的都知道取余hash&oldCap-1,那么扩容后取余方式hash&newCap-1,举个实际的例子
12%4,二进制1100 & 11 = 0 10进制0
12%8,二进制1100 & 1 11 = 100 10进制4
实际上,扩容后取余方式就是在前面又补1,后面的11都没用上(对于2n次幂肯定一样);那么扩容后这个元素是否需要移动到扩容后槽中,其实就看(newCap-1)最高位就行。既然我们只需要看这一位那我就把hash其他位全变成零,1 11中后面11换成0根hash位与操作就行,刚好(newCap-1)除了高位一外其他位换0就是oldCap。所以这er要么是0要么就是oldCap,需要往新槽里面移动的,只需要在原槽基础上加oldCap就可以了。
删除
-
public V remove(Object key) {
-
Node<K,V> e;
-
return (e = removeNode(hash(key), key,
null,
false,
true)) ==
null ?
null : e.value;
-
}
-
public boolean remove(Object key, Object value) {
-
return removeNode(hash(key), key, value,
true,
true) !=
null;
-
}
-
// matchValue 为true,则表示删除它key对应的value,不删除key,
-
// movable 为false,则表示删除后,不移动节点
-
final Node<K,V> removeNode(int hash, Object key, Object value,
-
boolean matchValue,
boolean movable) {
-
Node<K,V>[] tab; Node<K,V> p;
int n, index;
-
// 哈希数组不为null,且长度大于0,然后获得到要删除key的节点所在是数组下标位置
-
if ((tab = table) !=
null && (n = tab.length) >
0 &&
-
(p = tab[index = (n -
1) & hash]) !=
null) {
-
// nodee 存储要删除的节点,e 临时变量,k 当前节点的key,v 当前节点的value
-
Node<K,V> node =
null, e; K k; V v;
-
// 如果数组下标的节点正好是要删除的节点,把值赋给临时变量node
-
if (p.hash == hash &&
-
((k = p.key) == key || (key !=
null && key.equals(k))))
-
node = p;
-
// 链表或者红黑树
-
else
if ((e = p.next) !=
null) {
-
if (p
instanceof TreeNode)
-
// 遍历红黑树,找到该节点并返回
-
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
-
else {
// 表示为链表节点,一样的遍历找到该节点
-
do {
-
if (e.hash == hash &&
-
((k = e.key) == key ||
-
(key !=
null && key.equals(k)))) {
-
node = e;
-
break;
-
}
-
// 如果进入了链表中的遍历,那么此处的p不再是数组下标的节点,而是要删除结点的上一个结点
-
p = e;
-
}
while ((e = e.next) !=
null);
-
}
-
}
-
// 找到要删除的节点后,判断!matchValue,我们正常的remove删除,!matchValue都为true
-
if (node !=
null && (!matchValue || (v = node.value) == value ||
-
(value !=
null && value.equals(v)))) {
-
// 如果删除的节点是红黑树结构,则去红黑树中删除
-
if (node
instanceof TreeNode)
-
((TreeNode<K,V>)node).removeTreeNode(
this, tab, movable);
-
// 如果是链表结构,且删除的节点为数组下标节点,也就是头结点,直接让下一个作为头
-
else
if (node == p)
-
tab[index] = node.next;
-
else
// 为链表结构,删除的节点在链表中,把要删除的下一个结点设为上一个结点的下一个节点
-
p.next = node.next;
-
// 修改计数器
-
++modCount;
-
// 长度减1
-
--size;
-
// 此方法在hashMap中是为了让子类去实现,主要是对删除结点后的链表关系进行处理
-
afterNodeRemoval(node);
-
// 返回删除的节点
-
return node;
-
}
-
}
-
// 返回null则表示没有该节点,删除失败
-
return
null;
-
}
清除重置
-
public void clear() {
-
Node<K,V>[] tab;
-
modCount++;
-
// 数组不为空
-
if ((tab = table) !=
null && size >
0) {
-
size =
0;
-
// 遍历数组
-
for (
int i =
0; i < tab.length; ++i)
-
// 所有槽清空
-
tab[i] =
null;
-
}
-
}
可以看出,HashMap并没有清除所有元素,只是清空所有槽,并且不会改变槽个数
-
void reinitialize() {
-
table =
null;
-
entrySet =
null;
-
keySet =
null;
-
values =
null;
-
modCount =
0;
-
threshold =
0;
-
size =
0;
-
}
清空所有数据
创建、转型节点
-
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
-
return
new Node<>(hash, key, value, next);
-
}
创建新普通节点
-
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
-
return
new Node<>(p.hash, p.key, p.value, next);
-
}
其他节点转换为普通节点
-
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
-
return
new TreeNode<>(hash, key, value, next);
-
}
创建树节点
-
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
-
return
new TreeNode<>(p.hash, p.key, p.value, next);
-
}
普通节点转换为树节点
替换
-
public boolean replace(K key, V oldValue, V newValue) {
-
Node<K,V> e; V v;
-
// 根据key查找节点,如果节点的值不为oldValue时放弃修改
-
if ((e = getNode(hash(key), key)) !=
null &&
-
((v = e.value) == oldValue || (v !=
null && v.equals(oldValue)))) {
-
e.value = newValue;
-
afterNodeAccess(e);
-
return
true;
-
}
-
return
false;
-
}
只有在key存在,并且key对应的值跟oldValue一样时,才会替换
-
public V replace(K key, V value) {
-
Node<K,V> e;
-
// 根据key查找节点
-
if ((e = getNode(hash(key), key)) !=
null) {
-
V oldValue = e.value;
-
// 新值覆盖原值
-
e.value = value;
-
afterNodeAccess(e);
-
// 返回原值
-
return oldValue;
-
}
-
return
null;
-
}
找到节点就覆盖,找不到就返回空
重要内部类
Node类
static class Node<K,V> implements Map.Entry<K,V>
Node属性
-
// hash值
-
final
int hash;
-
// 键
-
final K key;
-
// 值
-
V value;
-
// 下一个元素
-
Node<K,V> next;
Node构造函数
-
Node(
int hash, K key, V value, Node<K,V> next) {
-
this.hash = hash;
-
this.key = key;
-
this.value = value;
-
this.next = next;
-
}
Node构造函数只负责初始化内部属性
Node方法
-
public final K getKey() {
return key; }
-
public final V getValue() {
return value; }
-
public final String toString() {
return key +
"=" + value; }
-
public final V setValue(V newValue) {
-
V oldValue = value;
-
value = newValue;
-
return oldValue;
-
}
可以看出只能更改值不能更改键
-
public final int hashCode() {
-
return Objects.hashCode(key) ^ Objects.hashCode(value);
-
}
hashcode方法比较特殊,键的hashcode和值的hashcode异或
-
public final boolean equals(Object o) {
-
if (o ==
this)
-
return
true;
-
if (o
instanceof Map.Entry) {
-
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
-
if (Objects.equals(key, e.getKey()) &&
-
Objects.equals(value, e.getValue()))
-
return
true;
-
}
-
return
false;
-
}
equals方法其实就是比较键和值是否一样
TreeNode类
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>
不理解红黑树,看TreeNode源码比较吃力,这里顺带介绍下红黑树。
红黑树的五大特性:
-
1、每个结点是黑色或者红色。
-
2、根结点是黑色。
-
3、每个叶子结点(
NIL)是黑色。(这里叶子结点,是指为空(
NIL或NULL)的叶子结点!)
-
4、如果一个结点是红色的,则它的子结点必须是黑色的。
-
5、每个结点到叶子结点
NIL所经过的黑色结点的个数一样的。(确保没有一条路径会比
-
其他路径长出俩倍,所以红黑树是相对接近平衡的二叉树的!)
红黑树基本操作:
-
左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,
-
右子结点的左子结点变为旋转结点的右子结点,其左子结点保持不变。
-
右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,
-
左子结点的右子结点变为旋转结点的左子结点,其右子结点保持不变。
-
变色:结点的颜色由红变黑或由黑变红。
LinkedHashMap.Entry类
-
static
class Entry<K,V> extends HashMap.Node<K,V> {
-
Entry<K,V> before, after;
-
// 调用HashMap.Node构造函数
-
Entry(
int hash, K key, V value, Node<K,V> next) {
-
super(hash, key, value, next);
-
}
-
}
HashMap.Node前面已经讲过了,往上翻
TreeNode属性
-
// 父节点
-
TreeNode<K,V> parent;
-
// 左节点
-
TreeNode<K,V> left;
-
// 又结点
-
TreeNode<K,V> right;
-
// 前面结点
-
TreeNode<K,V> prev;
-
// 当前结点颜色
-
boolean red;
-
// LinkedHashMap.Entry中上一个元素(双向链表)
-
Entry<K,V> before;
-
// LinkedHashMap.Entry中下一个元素(双向链表)
-
Entry<K,V> after;
-
// hash值
-
final
int hash;
-
// 键
-
final K key;
-
// 值
-
V value;
-
// 下一个元素
-
Node<K,V> next;
TreeNode构造函数
-
TreeNode(
int hash, K key, V val, Node<K,V> next) {
-
// 调用LinkedHashMap.Entry中构造函数
-
super(hash, key, val, next);
-
}
TreeNode查找root结点
-
final TreeNode<K,V> root() {
-
// 这里声明两个变量r、p,当前结点赋值给r,然后迭代
-
for (TreeNode<K,V> r =
this, p;;) {
-
// 取当前结点的父节点赋值p,判断是否为空
-
if ((p = r.parent) ==
null)
-
// 没有父节点就是root结点
-
return r;
-
// p赋值给r继续迭代
-
r = p;
-
}
-
}
TreeNode的find方法
-
// h 为键的hash值,k 为键,kc 为比较器(实现了Comparable接口)
-
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
-
// 声明变量p,并把当前节点赋值给它
-
TreeNode<K,V> p =
this;
-
do {
-
// 声明ph存放临时节点hash,dir 临时比较的结果,pk临时节点键
-
int ph, dir; K pk;
-
// pl 左节点,pr右节点,q下轮要找的节点
-
TreeNode<K,V> pl = p.left, pr = p.right, q;
-
// 把当前节点hash赋值给ph,判断当前节点hash是否大于h
-
if ((ph = p.hash) > h)
-
// 大于说明要找的节点在左边
-
p = pl;
-
else
if (ph < h)
-
// ph 小于h,说明要找的在右边
-
p = pr;
-
-
// 能走到这,至少说明hash一样了
-
else
if ((pk = p.key) == k || (k !=
null && k.equals(pk)))
-
// 把当前节点键赋值给pk,然后判断是否一样,一样直接就返回
-
return p;
-
-
// 能走这儿,说明hash一样key不一样
-
else
if (pl ==
null)
-
// 左边为空赋值右边,希望寄托下一轮
-
p = pr;
-
else
if (pr ==
null)
-
// 右边边为空赋值左边,希望寄托下一轮
-
p = pl;
-
-
// 能走这儿,说明hash一样,key不一样,还都有子节点
-
// comparableClassFor 获取键的比较器
-
// compareComparables 获取比较结果
-
// 判断比较器是否为空,为空获取k的比较器,然后比较大小
-
else
if ((kc !=
null ||
-
(kc = comparableClassFor(k)) !=
null) &&
-
(dir = compareComparables(kc, k, pk)) !=
0)
-
// 比较结果小于0走左边,大于零走右边,等于0还是左右不分
-
p = (dir <
0) ? pl : pr;
-
-
// 前面比较器比较结果也一样,尝试右边查一把
-
else
if ((q = pr.find(h, k, kc)) !=
null)
-
// 查到了就返回
-
return q;
-
else
-
// 没查到,从左边继续下一轮查
-
p = pl;
-
}
while (p !=
null);
-
// 都查完了还是空,只能返回没查到
-
return
null;
-
}
find方法使用比较多所以这里先做分析
TreeNode的getTreeNode方法
-
final TreeNode<K,V> getTreeNode(int h, Object k) {
-
return ((parent !=
null) ? root() :
this).find(h, k,
null);
-
}
这里先判断自己是不是root结点,如果是就从自身开始找,如果不是先找root,然后从root开始找TreeNode添加
-
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
-
int h, K k, V v) {
-
// 比较器
-
Class<?> kc =
null;
-
// 是否搜索过(只有在hash和比较器都不能确定时才会用)
-
boolean searched =
false;
-
// 获取头结点
-
TreeNode<K,V> root = (parent !=
null) ? root() :
this;
-
// 从头结点开始遍历
-
for (TreeNode<K,V> p = root;;) {
-
// dir 比较器比较结果,ph 当前临时hash,pk当前临时键
-
int dir, ph; K pk;
-
if ((ph = p.hash) > h)
-
// 当前hash大于h,走左边
-
dir = -
1;
-
else
if (ph < h)
-
// 当前hash小于h,走右边
-
dir =
1;
-
else
if ((pk = p.key) == k || (k !=
null && k.equals(pk)))
-
// 键已存在
-
return p;
-
// 相同hash,键不等,通过比较器确定
-
else
if ((kc ==
null &&
-
(kc = comparableClassFor(k)) ==
null) ||
-
(dir = compareComparables(kc, k, pk)) ==
0) {
-
// 比较器比较也一样
-
if (!searched) {
// 没搜索过
-
TreeNode<K,V> q, ch;
-
// 设置搜索标志
-
searched =
true;
-
// 先搜索左边,没搜到,在搜索右边(find方法前面有将)
-
if (((ch = p.left) !=
null &&
-
(q = ch.find(h, k, kc)) !=
null) ||
-
((ch = p.right) !=
null &&
-
(q = ch.find(h, k, kc)) !=
null))
-
// 搜索到就返回q
-
return q;
-
}
-
// 搜索过或者没搜到,只能通过物理hash大小确定左右
-
dir = tieBreakOrder(k, pk);
-
}
-
-
// 走这儿说明没有重复建
-
TreeNode<K,V> xp = p;
-
// 这里一定可以确定左右(dir只能是-1或1,为0前面已经转掉了)
-
if ((p = (dir <=
0) ? p.left : p.right) ==
null) {
-
// 能到这儿,说明有一边为空,
-
Node<K,V> xpn = xp.next;
// 兼容双向原链表结构
-
// 创建新树节点,原下级链表接在此节点后
-
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
-
// 新节点上树
-
if (dir <=
0)
-
xp.left = x;
-
else
-
xp.right = x;
-
// 维护双向链表下级节点
-
xp.next = x;
-
// 维护树父级节点,维护双向链表前面结点
-
x.parent = x.prev = xp;
-
// 当前结点下级几点不为空时,需要把下级节点前面指向x
-
if (xpn !=
null)
-
((TreeNode<K,V>)xpn).prev = x;
-
// 平衡红黑树
-
moveRootToFront(tab, balanceInsertion(root, x));
-
// 添加成功返回空
-
return
null;
-
}
-
}
-
}
TreeNode删除
-
// 此方法只有HashMap删除时找到应该删除的结点为树节点是才会调用
-
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
-
boolean movable) {
-
int n;
-
// 数组为空时直接返回
-
if (tab ==
null || (n = tab.length) ==
0)
-
return;
-
// 确定当前元素所在的槽
-
int index = (n -
1) & hash;
-
// first、root临时当前槽头节点
-
TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
-
// succ临时当前节点的下级节点, pred临时当前节点前面节点
-
TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
-
// 删除的节点是头节点
-
if (pred ==
null)
-
tab[index] = first = succ;
-
else
-
// 不是头节点,上级节点下级指向当前下级
-
pred.next = succ;
-
// 不是尾节点,把下级节点的上级指向当前上级
-
if (succ !=
null)
-
succ.prev = pred;
-
// --走到这双向链表中该节点已经删除成功--
-
-
// 头节点为空直接返回
-
if (first ==
null)
-
return;
-
// 头节点上级不为空
-
if (root.parent !=
null)
-
// 重新找root节点
-
root = root.root();
-
if (root ==
null
// 头节点为空
-
|| (movable
-
&& (root.right ==
null
// 头节点右为空
-
|| (rl = root.left) ==
null
// 头节点左为空
-
|| rl.left ==
null))) {
// 左节点的左节点为空
-
// 树链表,树转链表依赖双向链表,不依赖树结构(这种情况无视长度小于6?)
-
tab[index] = first.untreeify(map);
// too small
-
return;
-
}
-
// 下面开始从树结构中移除当前元素
-
TreeNode<K,V> p =
this, pl = left, pr = right, replacement;
-
// 左右都不为空
-
if (pl !=
null && pr !=
null) {
-
// 找到右节点下最左边节点(循环左边的左边)
-
// 右边所有子节点中,最左边的一定最接近当前节点(可以自己推)
-
TreeNode<K,V> s = pr, sl;
-
while ((sl = s.left) !=
null)
// find successor
-
s = sl;
-
// 交换p(当前节点)和s(右边最左下)的颜色
-
boolean c = s.red; s.red = p.red; p.red = c;
// swap colors
-
TreeNode<K,V> sr = s.right;
-
TreeNode<K,V> pp = p.parent;
-
// 当前节点的右节点没有左节点(简单交换位置)
-
if (s == pr) {
-
// 当前上级指向s
-
p.parent = s;
-
// 当前节点放到s右边
-
s.right = p;
-
}
-
else {
-
// s节点和当前节点互换位置并设置父节点
-
TreeNode<K,V> sp = s.parent;
-
if ((p.parent = sp) !=
null) {
-
if (s == sp.left)
-
sp.left = p;
-
else
-
sp.right = p;
-
}
-
if ((s.right = pr) !=
null)
-
pr.parent = s;
-
}
-
p.left =
null;
-
// 如果s有右节点,把p设置成sr的父节点
-
if ((p.right = sr) !=
null)
-
sr.parent = p;
-
// 把p的左节点交接给s
-
if ((s.left = pl) !=
null)
-
pl.parent = s;
-
// 把p的父节点交接给s
-
if ((s.parent = pp) ==
null)
-
root = s;
-
else
if (p == pp.left)
-
pp.left = s;
-
else
-
pp.right = s;
-
// 如果sr不为null替换p的节点就是sr,否则为p
-
if (sr !=
null)
-
replacement = sr;
-
else
-
replacement = p;
-
}
-
// 左右空的情况
-
else
if (pl !=
null)
-
replacement = pl;
-
else
if (pr !=
null)
-
replacement = pr;
-
else
-
replacement = p;
-
-
if (replacement != p) {
-
// 把要删除的移除掉
-
TreeNode<K,V> pp = replacement.parent = p.parent;
-
if (pp ==
null)
-
root = replacement;
-
else
if (p == pp.left)
-
pp.left = replacement;
-
else
-
pp.right = replacement;
-
p.left = p.right = p.parent =
null;
-
}
-
-
TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);
-
-
if (replacement == p) {
// detach
-
// 移除当前节点
-
TreeNode<K,V> pp = p.parent;
-
p.parent =
null;
-
if (pp !=
null) {
-
if (p == pp.left)
-
pp.left =
null;
-
else
if (p == pp.right)
-
pp.right =
null;
-
}
-
}
-
// 是否需要平衡树
-
if (movable)
-
moveRootToFront(tab, r);
-
}
树转链表
-
final Node<K,V> untreeify(HashMap<K,V> map) {
-
Node<K,V> hd =
null, tl =
null;
-
// 这里只依赖链表转换
-
for (Node<K,V> q =
this; q !=
null; q = q.next) {
-
// TreeNode将转化成Node
-
Node<K,V> p = map.replacementNode(q,
null);
-
if (tl ==
null)
-
hd = p;
-
else
-
tl.next = p;
-
tl = p;
-
}
-
return hd;
-
}
TreeNode扩容迁移节点
-
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
-
TreeNode<K,V> b =
this;
-
// loHead 扩容前头节点,loTail扩容前临时结点
-
// hiHead 扩容前头节点,hiTail扩容前临时结点
-
TreeNode<K,V> loHead =
null, loTail =
null;
-
TreeNode<K,V> hiHead =
null, hiTail =
null;
-
// lc原槽中元素个数, hc扩容槽元素个数
-
int lc =
0, hc =
0;
-
for (TreeNode<K,V> e = b, next; e !=
null; e = next) {
-
next = (TreeNode<K,V>)e.next;
-
e.next =
null;
-
if ((e.hash & bit) ==
0) {
-
if ((e.prev = loTail) ==
null)
-
loHead = e;
-
else
-
loTail.next = e;
-
loTail = e;
-
++lc;
-
}
-
else {
-
if ((e.prev = hiTail) ==
null)
-
hiHead = e;
-
else
-
hiTail.next = e;
-
hiTail = e;
-
++hc;
-
}
-
}
-
if (loHead !=
null) {
-
// 原槽元素个数小于等于6,树转链表
-
if (lc <= UNTREEIFY_THRESHOLD)
-
tab[index] = loHead.untreeify(map);
-
else {
-
tab[index] = loHead;
-
if (hiHead !=
null)
-
loHead.treeify(tab);
-
}
-
}
-
if (hiHead !=
null) {
-
// 扩容槽中元素个数小于等于6,树转链表
-
if (hc <= UNTREEIFY_THRESHOLD)
-
tab[index + bit] = hiHead.untreeify(map);
-
else {
-
tab[index + bit] = hiHead;
-
if (loHead !=
null)
-
hiHead.treeify(tab);
-
}
-
}
-
}
双向链表转树
调用此方法前必须先构建双向链表
-
final void treeify(Node<K,V>[] tab) {
-
TreeNode<K,V> root =
null;
-
for (TreeNode<K,V> x =
this, next; x !=
null; x = next) {
-
next = (TreeNode<K,V>)x.next;
-
x.left = x.right =
null;
-
// 插入的是第一个元素 并给root赋值
-
if (root ==
null) {
-
x.parent =
null;
-
x.red =
false;
-
root = x;
-
}
-
else {
-
K k = x.key;
-
int h = x.hash;
-
Class<?> kc =
null;
-
//插入到红黑树中的位置 逻辑跟putTreeVal类似
-
for (TreeNode<K,V> p = root;;) {
-
int dir, ph;
-
K pk = p.key;
-
if ((ph = p.hash) > h)
-
dir = -
1;
-
else
if (ph < h)
-
dir =
1;
-
else
if ((kc ==
null &&
-
(kc = comparableClassFor(k)) ==
null) ||
-
(dir = compareComparables(kc, k, pk)) ==
0)
-
dir = tieBreakOrder(k, pk);
-
-
TreeNode<K,V> xp = p;
-
if ((p = (dir <=
0) ? p.left : p.right) ==
null) {
-
x.parent = xp;
-
if (dir <=
0)
-
xp.left = x;
-
else
-
xp.right = x;
-
root = balanceInsertion(root, x);
-
break;
-
}
-
}
-
}
-
}
-
// 把root节点移到链表头
-
moveRootToFront(tab, root);
-
}
头节点前移
-
static <K,V>
void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
-
int n;
-
if (root !=
null && tab !=
null && (n = tab.length) >
0) {
-
// 确定槽位置
-
int index = (n -
1) & root.hash;
-
TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
-
// 如果链表的头不是红黑树的头节点 则把root移到头节点 也就是first的前面
-
if (root != first) {
-
Node<K,V> rn;
-
tab[index] = root;
-
TreeNode<K,V> rp = root.prev;
-
if ((rn = root.next) !=
null)
-
((TreeNode<K,V>)rn).prev = rp;
-
if (rp !=
null)
-
rp.next = rn;
-
if (first !=
null)
-
first.prev = root;
-
root.next = first;
-
root.prev =
null;
-
}
-
// 检查一下红黑树的各个成员变量是否正常
-
assert checkInvariants(root);
-
}
-
}
moveRootToFront的作用就是把root节点move到链表的头.
转载:https://blog.csdn.net/nan8426/article/details/72898204