存放机制
1.初始化表格
默认情况构建HashMap时,如果表格table为Null,那么首先先new 一个table数组出来,里面存储的都是node
2.基于映射找到节点
存放时基于 key 和 value的键值对
首先基于key进行哈希函数的计算 将结果与table容量-1进行与运算 得到在table数组中的索引位置
3.添加或者更新节点
table数组中对应的位置有三种情况
对应位置为空---> 直接new Node() 把key ,value,哈希值,next全部封装到Node里面,然后把Node对象放到数组对应位置。
对应位置已经存放了Node对象,那么就遍历链表,如果在遍历过程中找到了hasCode和key值与添加节点相同,那么直接替换,如果没有找到相同的节点,那么直接将新节点添加到链表的末尾。在添加过程中,链表长度如果大于树化的阈值,树化的阈值默认为8,table的容量默认大于64时才能树化。容量没有达到64,要先扩容到64 然后再树化
对应位置是TreeNode ,那么就会基于红黑树的操作,将节点添加到红黑树里面,如果说红黑树里面已经key值相同的节点,就执行替换操作。
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) {
// i为数组下标 p为数组项中存放的链表
HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
//判断table是否为空,table对象是node数组,即hashmap中数据+链表+红黑树中的数组
if ((tab = table) == null || (n = tab.length) == 0)
//对hashmap的临界值和数组进行初始化
n = (tab = resize()).length;
//计算当前对象存放的下标,并判断数组中该下标是否已经存值
if ((p = tab[i = (n - 1) & hash]) == null)
///如果不存在则在该位置创建一个新的链表。并将目标值存入新链表的表头
tab[i] = newNode(hash, key, value, null);
else {
HashMap.Node<K,V> e; K k;
//判断目标key的hash值与链表头节点的hash值是否相等,它们的key是否相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判断p节点的类型是否是TreeNode
else if (p instanceof HashMap.TreeNode)
//如果红黑树节点中不存在与目标key相同的值,将目标对象插入红黑树中,存在相同的key则返回已存在的对象
e = ((HashMap.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);
//判断链表长度是否大于8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//将链表转化为红黑树
treeifyBin(tab, hash);
break;
}
//遍历的过程中发现链表中有key与目标对象的key相同
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//如果e不为空说明,hashmap中已经存在了当前目标对象的key,则将value进行覆盖
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
//修改值后,返回旧值
return oldValue;
}
}
//记录HashMap结构被修改的次数
++modCount;
//判断数组大小是否已经大于临界值了
if (++size > threshold)
//进行扩容
resize();
afterNodeInsertion(evict);
//Hashmap中不存在key,所以返回null
return null;
}
final void treeifyBin(HashMap.Node<K,V>[] tab, int hash) {
int n, index; HashMap.Node<K,V> e;
//如果数组大小小于64
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
//进行扩容
resize();
//链表结构转红黑树
else if ((e = tab[index = (n - 1) & hash]) != null) {
HashMap.TreeNode<K,V> hd = null, tl = null;
do {
HashMap.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);
}
}
4.超过阈值进行扩容
先添加再决定要不要扩容
整体表格的总元素数量超出阈值,对table进行扩容
扩容机制
扩容需要解决的问题?
1.计算新表格的大小,在原基础之上乘以2
2.遍历表、链、树中的元素,重新映射并拷贝至新位置