个人HASHMAP源码学习心得共勉 (不足之处,还望指出)
1.HashMap的原理,内部数据结构
A.JDK1.7是数组 + 链表
B.JDK1.8底层使用哈希表(数组+链表),当链表过长会将链表转换成红黑树加以实现
2.分析一下HashMap的put方法过程
A.对KEY求HASH值,然后在计算下标
B.如果没有碰撞,直接放入桶中
C.如果碰撞了 ,以链表的方式链接到后面
D.如果链表长度超过阀值(TREEIFY_THRESHOLD == 8 ),就把链表转换成红黑树
E.如果节点已经存在就替换
F.如果桶满了(容量(16) * 加载因子(0.75)),就需要resize(扩容)
hashmap的put的核心代码如一下代码所示
/*
hashMap中PUT(K,V)源码核心分析
hashmap中putVal( [hashmap-> put() --> putVal() ] )方法部分核心代码讲解
三种情况
1.KEY值相同,直接替换老的值就可以
2.KEY值不同,红黑树处理
3.KEY值不同,链表处理
*/
Node[] tab; Node p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0) //KEY值相同,直接替换老的值就可以
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)//KEY值不同,红黑树处理
tab[i] = newNode(hash, key, value, null);
else {//KEY值不同,链表处理
Node 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)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);//链表长度超过阀值(TREEIFY_THRESHOLD == 8 ),将链表转换成红黑树
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;
}
3.HashMap中的hash函数是怎么实现的?还有哪些hash的实现方式?
A.高16bit不变,低16bit和高16bit做一个异或
B.(n-1) & hash --> 得到下标
C.还有哪些HASH实现方式 :可以百度再看看
4.HashMap怎么解决冲突,讲一下扩容过程,假如一个值在原数组中,现在移动了新数组,位置肯定改变了,那是什么定位到在这个值新数组中的位置
A.将新节点加到链表之后
B.容量扩充为原来的两倍,然后对每个节点重新计算哈希值
C.这个值只可能在两个地方,一个是原下标的位置,另一种是在下标为<原下标 + 原容量>的位置
//hashmap核心的rresize()方法详解
final Node[] resize() {
Node[] oldTab = table;
//数组初始化 和 扩容 的时候判断
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
/*
判断原来数组有无超过数组的最大值 2^30
超过最大值 直接return出来
未超过最大值,直接进行扩容(如 double threshold所示代码段)
数组大小的扩容
数组容量 * 加载因子扩容
*/
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 {
/* 初始化的时候操作
数组初始化大小是16 = 2^4
以及需要扩容的大小
容量(16)
*
加载因子(0.75)
------------------
result 12
*/
// 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[] newTab = (Node[])new Node[newCap];
table = newTab;
/*
前提:必须要保证数组位置上的节点不为空
1.数组位置不为空
2.数组下标位置不为空
A.单纯的NODE节点
B.数组下标位置不为空,但下面为红黑树
C.数组下标位置不为空,但下面为链表
*/
if (oldTab != null) {//1.数组位置不为空
for (int j = 0; j < oldCap; ++j) {
Node e;
if ((e = oldTab[j]) != null) {//2.数组下标位置不为空
oldTab[j] = null;
if (e.next == null) //A.单纯的NODE节点
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)//B.数组下标位置不为空,但下面为红黑树
((TreeNode)e).split(this, newTab, j, oldCap);
else {//C.数组下标位置不为空,但下面为链表
//遍历链表具体如下操作
//主要是 新老数组 链表的对比操作
// preserve order
Node loHead = null, loTail = null;
Node hiHead = null, hiTail = null;
Node 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;
}
5.针对HashMap中的某个Entry链太长,查找时间复杂度可能达到0(n),怎么优化?
将链表转换成红黑树.JDK1.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;
}