目录
简介
空气中充满了消毒水的味道,四周都是白墙,墙上有一些泼墨山水画,明媚的阳光透过窗户晒进来,照在他的脸上
他躺在床上,马上就要做手术了,心情十分平静,也不记得是第多少次手术了.
她冲了进去,紧紧握着他的手:这次可不可以不要?
说着泪如雨下,头摇的跟拨浪鼓似的
他的指尖拂过她的脸颊, “傻瓜, 小手术而已. 不用担心”.
她只是不停的摇头,声音哽咽,好像这个手术之后会再也看不到他了.
作者: 行了行了,不就一个深度剖析吗,搞的跟生离死别似的.
HashMap先生做好深度剖析的准备了,你们准备好了吗?
版本介绍
JDK版本 |
1.8.0_102 |
重点
本文主要解决下面这几个疑问
1. HashMap 如何实现自动扩容?
2. 如何解决hash碰撞?
3. 是否线程安全?
4. HashMap的节点有几种形式?
构造简介
在构造这里,要搞清楚下面的三个概念
1. 容量是什么?
2. 加载因子是什么?
3. 阈值是什么?
默认构造
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
初始容量构造
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
将指定容量 和默认加载因子 给 初始容量 + 加载因子去加载
初始容量+加载因子构造
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
MAXIMUM_CAPACITY 上面的注释说明: 这是最大的容量,如果指定更大的值,就使用当前值,容量必须是2的次方,所以最大值是 2的30次方. 2的31次方会溢出
错误的值: NaN != NaN,NaN 可以是: 0.0f / 0.0f
构造总结
容量
容量是内部数组 table 的长度 table.length
容量的值一定是2的n次方,如果不是2的次方,在resize时会修改.
加载因子
加载因子是一个容量的阈值比例,根据加载因子修改阈值
阈值
阈值 = 容量 * 加载因子.
在进行以下操作时,会修改阈值
1. 重置集合大小时修改
2. 初始容量构造时修改
3. 初始容量+加载因子构造时修改
4. 复制目标集合构造时修改
5. 反序列化时修改
6. putAll 时修改
7. put 之后判断实际容量大于阈值时,重置集合大小,修改阈值
阈值-例1
默认容量为16,默认加载因子为0.75
状态 | 集合数量 | 实际容量 | 阈值 | 调用说明 |
初始状态 | 0 | 0 | 16 | 无参构造 |
增加一个(初次调用resize) | 1 | 16 | 12 = 16 * 0.75 | 第一次put,调用resize时 |
增加到13个 | 13 | 32 | 24 = 32 * 0.75 | 到达阈值,调用resize时 |
增加到25个 | 25 | 64 | 48 = 64 * 0.75 | 到达阈值,调用resize时 |
增加到49个 | 49 | 128 | 96 = 128 * 0.75 | 到达阈值,调用resize时 |
阈值-例2
指定容量为 5,默认加载因子为0.3 (虽然容量被指定为5,但是会进行 tableSizeFor,往上增加到最接近2的次方的值为:8)
状态 | 集合数量 | 实际容量 | 阈值 | 调用说明 |
初始状态 | 0 | 0 | 8 | 初始容量+加载因子 构造 |
增加1个( | 1 | 8 | 2 = 8 * 0.3 | 第一次put,调用resize时 |
增加到3个(到达阈值调用resize) | 3 | 16 | 4 = 16 * 0.3 | 到达阈值,调用resize时 |
增加到5个 | 5 | 32 | 9 = 32 * 0.3 | 到达阈值,调用resize时 |
增加到10个 | 10 | 64 | 19 = 64 * 0.3 | 到达阈值,调用resize时 |
阈值-例3
指定容量为 13,默认加载因子为0.90 (虽然容量被指定为13,但是会进行 tableSizeFor,往上增加到最接近2的次方的值为:16)
状态 | 集合数量 | 实际容量 | 阈值 | 调用说明 |
初始状态 | 0 | 0 | 16 | 初始容量+加载因子 构造 |
增加1个(初次调用resize) | 1 | 16 | 14 = 16 * 0.9 | 第一次put,调用resize时 |
增加到15个 | 15 | 32 | 28 = 32 * 0.9 | 到达阈值,调用resize时 |
增加到29个 | 29 | 64 | 57 = 64 * 0.9 | 到达阈值,调用resize时 |
增加到58个 | 58 | 64 | 115 = 128 * 0.9 | 到达阈值,调用resize时 |
前景提要
下文分析中的实体类: HashCollisionModel
hash碰撞实体
public class HashCollisionModel {
private Integer number;
public Integer getNumber() {
return number;
}
public HashCollisionModel(Integer number) {
this.number = number;
}
@Override
public boolean equals(Object o) {
if (this == o){ return true;}
if(o instanceof HashCollisionModel){
HashCollisionModel that = (HashCollisionModel) o;
return that.number .equals( number);
}
return false;
}
@Override
public int hashCode() {
return number%2+1;
}
}
put
是HashMap中最复杂的部分,接下来会根据他的节点模型来分析它
核心分析
获取key的hash
/**
* 将指定值与此映射中的指定键关联。
* 如果映射以前包含了键的映射,则值被替换
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这里首先对key取hash值,然后在去 putVal
putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 初始化检测,如果table 为null,就初始化
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// hash 未碰撞
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// hash 碰撞
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.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 检查是否超过阈值, 超过就重新设置大小
if (++size > threshold)
resize();
// 节点插入成功通知
afterNodeInsertion(evict);
return null;
}
内容描述
这里可以分为五个部分:
1. 初始化检测, 为空就重置大小
2. 没有hash碰撞时直接新建节点,并赋值
3. hash碰撞处理
4. 检查是否超过阈值, 超过就重置大小
5. 节点插入成功通知
hash碰撞描述
而hash碰撞又分三种情况
1. 首节点 hash和equals 都为true,旧值替换
2. 树化加入
3. 链表加入
加入链表描述
加入到链表又分三种
1. 遍历链表,hash&&equals为true时,此时不会加入到链表,而是进行旧值替换
2. 小于7个,加入链表
3. 大于等于第7个,进行树化
通过上述分析,得到流程图如下
put流程图
普通加入
先看下面的例子:
HashMap<Integer,Object> hashMap = new HashMap<>();
hashMap.put(1,"Paul");
这样 hashMap 内部就有一个key = 1 ,value=Paul 的一对值了
接下来分析PutVal 中的无hash碰撞
的代码块
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
本例的key=1,当前由于是Integer类型,hash就是它本身,本例中为 1
table[(16-1)&1] 就是table[1] == null.
table[1]没有内容,所以这里就直接新建一个节点就可以了
链表加入
接下来分析,putVal
中 加入链表
的部分
static final int TREEIFY_THRESHOLD = 8;
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;
}
//不是最后一个节点
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
判断自己是不是最后一个节点
是最后一个节点
1. 新建节点,设置自己的下一个节点为新建的节点
2. binCount大于等于7的时候,就会树化(treeifyBin)
如果不是最后一个节点:
将当前key和目标进行hash和equals比较,决定是否进入下一次循环
接下来演示加入链表的三个例子
首节点覆盖
例子:
HashMap<HashCollisionModel,Object> hashMap = new HashMap<>();
hashMap.put(new HashCollisionModel(1),"Edgar");//无hash碰撞
hashMap.put(new HashCollisionModel(3),"Helen");//hash碰撞
hashMap.put(new HashCollisionModel(1),"Paul");//覆盖首节点
链表首节点的 number = 1
链表图:
添加Edgar
索引 | 值 |
2 | Edgar |
添加Helen
索引 | 值 |
2 | Edgar->Helen |
添加Paul
索引 | 值 |
2 | Paul->Helen |
链表节点覆盖
HashMap<HashCollisionModel,Object> hashMap = new HashMap<>();
hashMap.put(new HashCollisionModel(3),"Helen");//未hash碰撞
hashMap.put(new HashCollisionModel(5),"Jim");//不是最后一个节点 && 不相等 加入链表
hashMap.put(new HashCollisionModel(5),"William");//不是最后一个节点 && 相等 替换旧值number=5
链表图:
索引 | 值 |
2 | Helen |
2 | Helen->Jim |
2 | Helen->William |
链表首节点的 number = 3
加入链表末尾
最后一个节点:
HashMap<HashCollisionModel,Object> hashMap = new HashMap<>();
hashMap.put(new HashCollisionModel(3),"Helen");//未hash碰撞
hashMap.put(new HashCollisionModel(5),"William");//是最后一个节点 && 不相等 加入链表
hashMap.put(new HashCollisionModel(7),"Jim");//是最后一个节点 加入链表
索引 | 值 |
2 | Helen |
2 | Helen->William |
2 | Helen->William->Jim |
树化加入
例子:
HashMap<HashCollisionModel,Object> hashMap = new HashMap<>();
hashMap.put(new HashCollisionModel(1),"Edgar");
hashMap.put(new HashCollisionModel(3),"Helen");
hashMap.put(new HashCollisionModel(5),"William");
hashMap.put(new HashCollisionModel(7),"Jim");
hashMap.put(new HashCollisionModel(9),"Kenneth");
hashMap.put(new HashCollisionModel(11),"Heathcliff");
hashMap.put(new HashCollisionModel(13),"Earnshaw");
hashMap.put(new HashCollisionModel(15),"Catherine");
hashMap.put(new HashCollisionModel(17),"Joseph");//树化resize
hashMap.put(new HashCollisionModel(19),"Linton");//树化resize
hashMap.put(new HashCollisionModel(21),"Lockwood");//树化 替换
hashMap.put(new HashCollisionModel(23),"Roth");//树化 替换
static final int MIN_TREEIFY_CAPACITY = 64;
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
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);
}
}
当容量小于 64 时,重置大小
大于等于64个时, 将所有节点替换为TreeNode对象
1. 从Node对象替换为TreeNode对象,并设置所有节点的 上一个和下一个节点对象
2. 设置树的根节点
3. 第一个节点对tab进行树化, (tab和this.table实际上是一个对象)
get
核心分析
源码:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
- 获取key的hash(本场景中 3的hash还是3)
- 判断getNode不为null,返回node.value
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// 表元素判断, 没有元素返回 null
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//链表第一个值, hash&&equals 都相等就返回
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 遍历链表, hash&&equals 都相等就返回
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
- 表元素判断, 没有元素返回 null
- 链表第一个值, hash&&equals 都相等就返回
- 如果已经树化,就执行树化获取
- 遍历链表, hash&&equals 都相等就返回
在这里发现,在put中的三种存入方式,获取时都做了相应的处理
普通获取
场景:
HashMap<Integer,Object> hashMap = new HashMap<>();
hashMap.put(3,"data");
hashMap.get(3);
链表获取
HashMap<HashCollisionModel,Object> hashMap = new HashMap<>();
hashMap.put(new HashCollisionModel(1),"Edgar");//无hash碰撞
hashMap.put(new HashCollisionModel(3),"Helen");//加入链表末尾
hashMap.get(new HashCollisionModel(3));//链表获取
此时会遍历节点的链表,执行do…while 代码块
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
树化获取
要树化获取,一定要先扩充到树化的容量
HashMap<HashCollisionModel,Object> hashMap = new HashMap<>();
hashMap.put(new HashCollisionModel(1),"Edgar");//无hash碰撞
hashMap.put(new HashCollisionModel(3),"Helen");//加入链表末尾
hashMap.put(new HashCollisionModel(5),"William");//加入链表末尾
hashMap.put(new HashCollisionModel(7),"Jim");//加入链表末尾
hashMap.put(new HashCollisionModel(9),"Kenneth");//加入链表末尾
hashMap.put(new HashCollisionModel(11),"Heathcliff");//加入链表末尾
hashMap.put(new HashCollisionModel(13),"Earnshaw");//加入链表末尾
hashMap.put(new HashCollisionModel(15),"Catherine");//加入链表末尾
hashMap.put(new HashCollisionModel(17),"Joseph");//加入链表末尾,扩充容量到32
hashMap.put(new HashCollisionModel(19),"Linton");//加入链表末尾,扩充容量到64
hashMap.put(new HashCollisionModel(21),"Lockwood");//树化 替换
hashMap.get(new HashCollisionModel(15));//树化获取
由于已经执行了树化替换的操作,那么所有节点已经是TreeNode,会执行 TreeNode的获取
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
remove
核心分析
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
- 获取hash
- removeNode 成功返回删除的值,删除失败返回null
接下来解析 removeNode
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;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
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 = e;
} while ((e = e.next) != null);
}
}
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;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
- 表数据验证 && 表数据索引验证
- 表索引位置的第一个值比较
- 遍历 表索引位置 的链表
- 是否找到目标值 && (是否匹配value || 找到的值.value == 目标value || 找到的值.value .equals 目标value)
- 删除成功通知
removeNode 流程图
普通删除
HashMap<Integer,Object> hashMap = new HashMap<>();
hashMap.put(3,"data");
hashMap.remove(3);
链表删除
HashMap<HashCollisionModel,Object> hashMap = new HashMap<>();
hashMap.put(new HashCollisionModel(3),"Helen");//未hash碰撞
hashMap.put(new HashCollisionModel(5),"William");//是最后一个节点 && 不相等 加入链表
hashMap.put(new HashCollisionModel(7),"Jim");//是最后一个节点 加入链表
hashMap.remove(new HashCollisionModel(7));
树化删除
HashMap<HashCollisionModel,Object> hashMap = new HashMap<>();
hashMap.put(new HashCollisionModel(1),"Edgar");//无hash碰撞
hashMap.put(new HashCollisionModel(3),"Helen");//加入链表末尾
hashMap.put(new HashCollisionModel(5),"William");//加入链表末尾
hashMap.put(new HashCollisionModel(7),"Jim");//加入链表末尾
hashMap.put(new HashCollisionModel(9),"Kenneth");//加入链表末尾
hashMap.put(new HashCollisionModel(11),"Heathcliff");//加入链表末尾
hashMap.put(new HashCollisionModel(13),"Earnshaw");//加入链表末尾
hashMap.put(new HashCollisionModel(15),"Catherine");//加入链表末尾
hashMap.put(new HashCollisionModel(17),"Joseph");//加入链表末尾,扩充容量到32
hashMap.put(new HashCollisionModel(19),"Linton");//加入链表末尾,扩充容量到64
hashMap.put(new HashCollisionModel(21),"Lockwood");//树化 替换
hashMap.remove(new HashCollisionModel(15));//树化删除
一旦树化后,不会因为删除对象而退回为链表
线程安全
@Test
public void testThreadSafe() throws InterruptedException {
String EMPTY_VALUE ="";
HashMap<Integer, Object> hashMap = new HashMap<>();
hashMap.put(1, EMPTY_VALUE);
Thread threadA = new Thread(() -> {
Iterator<Integer> iterator = hashMap.keySet().iterator();
System.out.println("进入 iterator 临界区");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (iterator.hasNext()){
Integer key = iterator.next();
System.out.println(key);
}
System.out.println("并发判断");
});
Thread threadB = new Thread(() -> {
System.out.println("并发插入节点3");
hashMap.put(3,EMPTY_VALUE);
});
threadA.start();
Thread.sleep(1000);
threadB.start();
threadA.join();
threadB.join();
}
这里用两个线程对 hashMap 操作.按照如下步骤产生并发插入
1. threadA 调用 keySet/values/entrySet 的 iterator,保存modCount进入临界区
2. threadB 执行插入
3. 产生 ConcurrentModificationException 异常
这里以keySet为例,对临界区进行一个详细深入的分析.
1. keySet 会创建一个继承AbstractSet 的对象,重写了iterator,返回一个KeyIterator对象
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}
final class KeySet extends AbstractSet<K> {
public final Iterator<K> iterator() { return new KeyIterator(); }
}
- KeyIterator 是一个继承 HashIterator 的一个类,在构造时,保存modCount,并且重写Iterator的next(),转发给nextNode().key
final class KeyIterator extends HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}
abstract class HashIterator {
// 省略其他无关代码
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
}
- 并发验证,modCount 被修改时,抛出异常:ConcurrentModificationException
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
那么现在你是否了解put时,modCount++这句话的意义了吗?
总结
- 通过
阈值
自动扩容 - 用链表和树化解决Hash碰撞
- 不是线程安全
- HashMap的节点模型有:单节点,链表,树,三种形式
节点模型
在hashMap 中,数组的节点可能会有下面三种形态,但是数组中的某一个节点只能有一个形态
1. 无hash碰撞
2. 链表模型
3. 树化模型
无hash碰撞模型
链表模型
树化模型
拓展
旧值替换
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
旧值替换不一定会替换旧值,接下来分别演示替换成功和替换失败的两种情况
替换旧值成功
例1: 首节点替换
HashMap<HashCollisionModel,Object> hashMap = new HashMap<>();
hashMap.put(new HashCollisionModel(3),"Jim");//未hash碰撞
hashMap.put(new HashCollisionModel(3),"William");//不是最后一个节点 && 相等
例2: 链表节点替换
HashMap<HashCollisionModel,Object> hashMap = new HashMap<>();
hashMap.putIfAbsent(new HashCollisionModel(1),"Edgar");//未hash碰撞
hashMap.putIfAbsent(new HashCollisionModel(3),"Helen");//不是最后一个节点 && 相等
hashMap.putIfAbsent(new HashCollisionModel(3),"William");//不是最后一个节点 && 相等
例3 : value为null时,无论首节点还是子节点都会被替换
HashMap<HashCollisionModel,Object> hashMap = new HashMap<>();
hashMap.putIfAbsent(new HashCollisionModel(3),null);//未hash碰撞
hashMap.putIfAbsent(new HashCollisionModel(3),"William");//不是最后一个节点 && 相等
替换旧值失败
例1 : 已经存在,替换失败
HashMap<HashCollisionModel,Object> hashMap = new HashMap<>();
hashMap.putIfAbsent(new HashCollisionModel(3),"Jim");//未hash碰撞
hashMap.putIfAbsent(new HashCollisionModel(3),"William");//不是最后一个节点 && 相等
重置大小
核心解析
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
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<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
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;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
这里的重置大小会有多种情形,但是无论是哪种情形,
都是一个对内部数组table的长度修改的一个操作.
重点就在于
1. 扩容的容量是多少?
2. 扩容后对于搜索有什么好处
接下来分两种情况去分析
第一次put
例子:
HashMap<Integer, Object> hashMap = new HashMap<>();
hashMap.put(1, "Edgar");//无hash碰撞
默认构造时的第一次put,此时只初始化了加载因子为0.75,此时进入else区,容量初始化为16,阈值为12
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
接着根据newCap 初始化 table = (Node
扩容
HashMap<Integer, Object> hashMap = new HashMap<>(8, 0.3F);
hashMap.put(1,"");//第一次扩容
hashMap.put(2,"");
hashMap.put(3,"");//第二次扩容
这里分析第二次扩容的情况,如何重置大小.
- 进入下面这个语句块,设置新容量 = 旧容量*2
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//设置新容量 = 旧容量*2
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
//旧容量为8, 8>=16的判断为false,不会为newThr赋值
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
- 根据新容量,设置新的阈值
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
扩容了之后,接下来就是要移动内部所有的元素了,用下面的伪代码演示,如何移动所有的节点
if (oldTab != null) {
if(table[i]!=null){
if(是单节点){
// TODO 单节点移动
newTab[e.hash & (newCap - 1)] = e;
}else if(树化节点){
// TODO 树化节点移动
}else if(链表节点){
// TODO 链表节点移动
}
}
}
接下来简单的介绍,单节点移动的table的内容
单节点移动
HashMap<Integer, Object> hashMap = new HashMap<>(16, 0.3F);
hashMap.put(1,"");//第一次扩容
hashMap.put(8,"");
hashMap.put(24,"");
hashMap.put(25,"");
hashMap.put(26,"");//第二次扩容
第二次扩容之前的table的内容
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | … | 15 | 16 |
1 | 8->24 | 25 |
第二次扩容的table的内容
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | … | 24 | 25 | 26 | … | 32 |
1 | 8 | 24 | 25 | 26 |