HashMap
1.键值对存储,允许为空,key值唯一,如果相同就覆盖
2.线程不安全
3.底层是hash表,不是有序的
4.使用数组加链表加红黑树存储
插入过程(put)
1.调用自己的hash方法
(n - 1) & hash//使用与运算的方式快速取模
//16 - 1 1111(15)
//hash = 6 0110
//& 0110(6)
//hash = 18 10010
//& 0010(2)
//使用与运算效率更高,n数组的长度必须是2的幂次方
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2.如果数组为空,调用resize()方法初始化数组
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
3.通过hash方法计算到的hash值,与数组长度减一取与运算,得到数组下标,如果数组位置为空,直接添加到当前位置
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
4.不为空,出现hash碰撞
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//1.p.hash == hash哈希值相等
//2.(k = p.key) == key地址比较相等
//3.key != null && key.equals(k)重写hash方法
4.1.如果key相同,覆盖旧值
e = p;
4.2.如果是红黑树结构,调用红黑树的插入方法
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
4.3.如果是链表结构,循环遍历直到某个节点为空,或者找到相同的key覆盖,判断是否要转换为红黑树
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;
}
4.4.如果是覆盖了hashmap的某个值,或者,返回旧值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
4.5.如果是在数组上的添加,判断是否需要扩容
if (++size > threshold)
resize();
获取过程(get)
1.调用自己的hash函数然后通过运算得到桶的位置,如果与桶的首位kay相同就直接放回值,否则遍历红黑树或者链表找到相同的key值并且返回对应的值
扩容过程
场景:
1.初始化数组
2.链表长度大于8,数组小于64
3.达到阈值时
扩容:
1.初始化默认长度16,创建长度为16的数组返回,正常扩容原来长度的两倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
2.桶里面只有一个元素的,与新的容量-1取与运算得到新的桶的位置下标,然后放入
3.桶的结构是红黑树
4.链表 遍历链表的每一个节点,如果该节点与旧的数组长度取与为零,则表明这个节点还在当前位置上,如果不为零就在当前位置加扩容的长度上,构建链表并插入。
//4 100
//8 1000
hash = 3 011 & 100 = 0
011 & 111 = 3
hash = 7 111 & 100 = 7
111 & 111 = 7 == 3(数组位置) + 4( )