HashMap
1. Hashtable 和 HashMap 的区别
* Hash table based implementation of the {@code Map} interface. This
* implementation provides all of the optional map operations, and permits
* {@code null} values and the {@code null} key. (The {@code HashMap}
* class is roughly equivalent to {@code Hashtable}, except that it is
* unsynchronized and permits nulls.) This class makes no guarantees as to
* the order of the map; in particular, it does not guarantee that the order
* will remain constant over time(特殊情况下并不能保证位置一直维持在以前的位置).
2. HashMap 第一次 put
2.1 代码
向一个新的 HashMap 中存入一个值
Map<String, String> map = new HashMap<>();
map.put("a", "a");
map.put("a", "c");
HashMap 的构造方法
static final float DEFAULT_LOAD_FACTOR = 0.75f;
final float loadFactor;
public HashMap() {
// 将默认加载因子常量赋值给 loadFactor 变量
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
调用 HashMap 的 put 方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// table 是一个 Node 类型的数组(全局变量),在 HashMap 中定义如下:
// transient Node<K,V>[] table;
// Node 结点定义如下:
/**
* static class Node<K,V> implements Map.Entry<K,V> {
* final int hash;
* final K key;
* V value;
* Node<K,V> next;
* }
*/
// 第一次调用 put 方法只走这个 if 分支(table isNull)
if ((tab = table) == null || (n = tab.length) == 0)
// 调用 resize() 方法初始化 tab
// n 初始化为 16
n = (tab = resize()).length;
// (16 - 1) 的二进制:1111
// 将 hash 码控制到 16 以内(0~15),相当于取模操作
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
// 省略。。。
}
++modCount; // 修改次数
// transient int size;
// 每次加 1,判断是否大于 threshold
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
resize() 方法
final Node<K,V>[] resize() {
// 第一次调用时 table 为 null
Node<K,V>[] oldTab = table;
// 初始化为 0
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// threshold(阈值)= table.length * 0.75 = 12
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
// 省略。。。
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
// static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 初始化数组长度为 16
newCap = DEFAULT_INITIAL_CAPACITY;
// 初始化阈值为 12
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
// 省略
}
// threshold 赋值 12
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
// 初始化 newTab 长度为 16
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
// 初始化 table 为 newTab
table = newTab;
if (oldTab != null) {
// 省略
}
// 返回长度为 16 的 node 数组给 tab
return newTab;
}
2.2 总结
- HashMap 第一次新建实例,构造方法初始化加载因子为 0.75
- 调用 put 方法,初始化 table 数组长度为 16 ,初始化 threshold 为 12
- key 的 hash 值取模 table 数组长度 16,存入 table 数组
- 返回 null
3. HashMap 设置同一个对象
3.1 代码
向 HashMap 中 put 相同 key
Map<String, String> map = new HashMap<>();
map.put("a", "a");
map.put("a", "c");
第二次调用 put 方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 第二次 put 跳过初始化 table
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// hash 码一致,所以 & 结果一致,能取出第一次数据,所以不为 null
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// hash 码一致,key 一致,说明为同一个 key
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// e 指向当前根据 key 取出的 brucket
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 省略。。。
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
// put 默认传入为 false
if (!onlyIfAbsent || oldValue == null)
// 修改 e 的 value 为传入 value
e.value = value;
afterNodeAccess(e);
// 返回 oldValue
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
3.2 总结
- 根据 key,从 table 中取出旧的 Node 对象
- 更换 value 为第二次 put value
- 返回 oldValue
4. hash 值相同但是对象不同
4.1 代码
假设 “a” 和 “xyz” hash 值相同,将 “a” 和 “xyz” 放入同一个 HashMap
Map<String, String> map = new HashMap<>();
map.put("a", "a");
map.put("xyz", "c");
将第二个对象放入 HashMap
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0) // 跳过初始化
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) // 存在第一次存放的 Node 对象,跳过
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash && // hash 码一致
((k = p.key) == key || (key != null && key.equals(k)))) // key 值不一样,跳过
e = p;
else if (p instanceof TreeNode) // TreeNode 是红黑树的结点,不为 TreeNode,跳过
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
// 将传入值赋给空的 p.next
p.next = newNode(hash, key, value, null);
// static final int TREEIFY_THRESHOLD = 8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break; // 直接 break 循环
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// e 为 null,跳过
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
4.2 总结
- 取出老的 hash 码相同的 bucket
- 循环 p.next 直至为 null
- 将 value 赋给 p.next
5. 链表下如果对象为同一个 HashMap 底层代码如何走
5.1 代码
HashMap<String, String> map = new HashMap<>();
map.put("a", "a");
map.put("xyz", "c");
map.put("xyz", "XX");
putValue
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0) // 跳过初始化
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) // 存在第一次存放的 Node 对象,跳过
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash && // hash 码一致
((k = p.key) == key || (key != null && key.equals(k)))) // key 值不一样,跳过
e = p;
else if (p instanceof TreeNode) // TreeNode 是红黑树的结点,不为 TreeNode,跳过
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 循环到此处,发现对象 hash 码相同,key 值相同,直接跳出循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// e 为 key 相同的 Node 对象
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
// 将值替换为传入 value
e.value = value;
afterNodeAccess(e);
return oldValue; // 返回 oldValue
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
5.2 总结
- 取出第一次存放的 hash 码相同的 bucket
- 循环链表节点直到 key 相同
- 将传入的值赋给老的key
- 返回 oldValue
6. 链表数据达到 9 是否转红黑树
6.1 代码
class Person {
public String name;
public Person(String name) { this.name = name; }
@Override
public int hashCode()
return 32512;
}
Map<Person, String> map = new HashMap<>();
map.put(new Person("a"), "p");
map.put(new Person("b"), "p");
map.put(new Person("c"), "p");
map.put(new Person("d"), "p");
map.put(new Person("e"), "p");
map.put(new Person("f"), "p");
map.put(new Person("g"), "p");
map.put(new Person("h"), "p");
map.put(new Person("k"), "p");
putVal 方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) // 此时 p 指向 person a
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = 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) { // 循环直至 p 指向 person h
p.next = newNode(hash, key, value, null); // person h 的 next 指向 person g
// 此时 binCount 达到 7
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 调用 treeifBin
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
链表达到9时调用 treeifBin() 方法
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// static final int MIN_TREEIFY_CAPACITY = 64
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) { // 并未走 else if 转红黑树
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
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);
}
}
6.2 总结
- 链表长度达到 9 调用 treeifBin() 方法
- tab.length == 16 小于 MIN_TREEIFY_CAPACITY 不转红黑树调用 resize() 方法
7. 链表结构超过 9 如何扩容
7.1 代码
接 6 调用 resize() 方法
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length; // 16
int oldThr = threshold; // 12
int newCap, newThr = 0; // 新的容量 新的阈值
if (oldCap > 0) { // true
if (oldCap >= MAXIMUM_CAPACITY) { // false
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && // newCap = oldCap * 2 = 32
oldCap >= DEFAULT_INITIAL_CAPACITY)
// oldThr * 2 = 24
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
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 数组长度为 32
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;
// 不为空,跳过
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 不为树节点,跳过
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
// low,high
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
// hash & oldCap 结果为 0 或者 16
if ((e.hash & oldCap) == 0) { // 放入低位
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else { // 16,放入高位
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;
}
7.2 总结
- 将阈值和数组容量扩容为两倍
- 复制老table到扩容后的table
8. HashMap 扩容后对整条链表的处理
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length; // 16
int oldThr = threshold; // 12
int newCap, newThr = 0; // 新的容量 新的阈值
if (oldCap > 0) { // true
if (oldCap >= MAXIMUM_CAPACITY) { // false
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && // newCap = oldCap * 2 = 32
oldCap >= DEFAULT_INITIAL_CAPACITY)
// oldThr * 2 = 24
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
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 数组长度为 32
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;
// 不为空,跳过
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 不为树节点,跳过
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
// low,high
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
// hash & oldCap 结果为 0 或者 16
if ((e.hash & oldCap) == 0) { // 放入低位
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else { // 16,放入高位
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead; // 把oldTab整条链放到32长度的newTab上
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
复制老table到扩容后的table
9. 当链表到达11个节点的时候转红黑树
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// static final int MIN_TREEIFY_CAPACITY = 64
// 第九个元素数组为 32
// put 第十个元素数组扩容为 64, 依旧 resize 把 oldTab 复制给 newTab
// put 第十一个元素跳过 if
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 {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
10. 数组扩容的时候链表如何处理
16 10000 & 15 = 0 10000 & 16 = 16
32 100000 0 0
48 110000 0 16
224 11100000 0 0
240 11110000 0 16
64 1010000 0 0
208 11010000 0 16
176 110000 0 16
352 110000 0 0
结论:扩容一倍后会拿出一半的数据,放入高位。
resize() 官方文档:
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.(要么呆在以前位置,要不* 然进行平移)
*
* @return the table
*/
看代码可知原理: resize() 中元素 hash 值 & oldCap,如果为零,放入低位链表串,不为零放入高位,因为在一个桶位的元素 hash 值与容量取模的值是一致的,容量二进制只有一个1(16,32,64),所以扩容按位与之后可以平分数据,保证链表的长度一致。之后低位在数组中依旧按老位置摆放,高位间隔老数组长度(平分老数组链表长度)。
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead; // 低位保持不变
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead; // 高位平移
}
11. 【完结】阈值如何控制扩容
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
if ((p = tab[i = (n - 1) & hash]) == null)
else {
}
++modCount;
if (++size > threshold) // 根据 key,value 对进行扩容,而不是桶位数量,key,value 对超过阈值,扩容
resize();
afterNodeInsertion(evict);
return null;
}
当数组长度扩容到 64,当一个链表长度达到 9 的时候,才会转红黑树。
11.1 总结
在一个桶位 put 超过 8 次,链表长度为 9 的时候,HashMap 会尝试转红黑树,如果这时候发现数组长度小于 64(MIN_TREEIFY_CAPACITY),会选择 resize() 扩容。但这是极端情况,一般情况通过阈值扩容,key value 插入超过阈值进行扩容,当有链表长度到达 9 的时候发现数组长度大于 64(阈值控制早已控制扩容到 64),会选择转红黑树。
当桶位中的链表长度达到 9 的时候,HashMap 会尝试把链表的结构转为红黑树,但是如果数组长度未达到 64,优先选择扩容数组,这是特殊情况(put 的时候逮住一个桶位 put,数组没有扩容)。一般情况是根据老师讲的 key,value 超过阈值,的情况数组已经扩容到 64,继续 put 数据,直到某一个桶位 put 第 9 个数据的时候 HashMap 尝试转红黑树,发现数组也达到 64,就会将桶位上的链表转为红黑树。