HashMap源码执行流程的整个过程
1)未传初始化容量参数,则直接创建HashMap并赋值加载因子为0.75,传容量参数,加载因子0.75没有变化,容量参数会进行赋值判断,结果必定是2的幂指数,如传9或者10,得到的初始容量参数都是16,new HashMap也就执行完了;
2)执行put方法,首先计算key的hashcode,然后右移16位得到key的hash值,在判断Node数组,也就是存放数据的地方是否初始化了,未进行初始化则初始化一个容量参数为16长度的Node数组;Node的属性我也说一下,有KEY、VALUE、HASH和NEXT属性,NEXT中还可以存放Node,也就是后面计算的hash值一样,但是key不一样的数据存放的地方,初始化Node后,会根据hash值和Node数组的大小减1进行&运算得到实际存放数据的下标,数组长度减1的目的也是防止索引越界;
3)如果对应的数组下标没有数据那就直接存进去,之后进行扩容判断,如果当前数组的大小已经大于负载因子和容量参数的乘积时会进行扩容,扩容的长度就是原来长度的1倍
4)如果对应的数组下标有数据,会判断是否是TreeNode节点,先说不是TreeNode的情况,不是TreeNode也就是我们说的链表,那就会循环链表,链表的长度小于8会在链表的尾部的Node节点属性的next中存进我们的新数据,如果此时的链条长度为8,加上我们的新Node数据就是9,则会进行转TreeNode树的操作,转TreeNode树前会首先进行扩容判断,Node数组容量小于64长度不进行转树操作而是进行扩容;扩容之后原来的数据会转到新的数组中,但是这也有需要注意的地方,如果原始数组中Node节点的Next属性中没有数据则是根据hash值和Node数组的大小减1进行&运算得到实际存放数据的下标直接存放,如果Node节点的Next属性不为空,首先会判断是否是TreeNode节点,先说不是TreeNode的情况,不是TreeNode也就是我们说的链表,由于数组下标是根据hash值和Node数组的大小减1进行&运算,扩容两倍后链表对应的数组下标会有两种情况,这个是计算机的知识,计算机存放数据都是0和1,数组长度扩容原始长度两倍,那最高位对应的值会发生变化,与运行的下标值要么为原始值,要么是原始值加上扩容的长度,那么链表就会一分为二存到Node数组中;如果是TreeNode,也是根据hash值和Node数组的大小减1进行&运算得出下标,同样也会出现两种情况,不过最终树节点的长度小于等于6,则退化为链表;
5)说一下红黑树吧,首先说一下红黑树的特性:
1. 每个节点都是红的或者是黑色的;
2. 根节点是黑色的;
3. 每个叶子节点是黑色的;
4. 如果一个节点是红色的,则它的两个儿子节点都是黑色的;
5. 对每个节点,从该节点到任何叶子节点的所有路径上包含相同数目的黑节点;
6. 新插入的节点都是红色的;
6)为了保持红黑树黑树的平衡,当有新节点插入时会有节点变色、左旋和右旋的情况;总结如下:
1. 父节点时黑色的,不用进行调整;
2. 父节点时红色,会有以下三种情况:
1 叔叔节点空的,右旋+变色;
2 叔叔节点是黑色的,左旋加变色;
3 叔叔节点是红色,父节点+叔叔节点变黑色,祖父节点变为红色;
HashMap源码分析
// put方法方法
public V put(K key, V value) {
// 根据key计算hashcode,相对于JDK7中hash算法有所简化
return putVal(hash(key), key, value, false, true);
}
// 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;
// 给tab赋值,并判断数组是否为null,如果是则初始化数组,并得到数组大小n
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 根据hashcode计算出对应的数组下标i,并判断该位置是否存在元素
// 如果为null,则生成一个Node对象赋值到该数组位置
// 否则,将该位置对应的元素取出来赋值给p
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
// 如果该下标位置存在元素,则进行一系列判断
Node<K, V> e;
K k;
// 首先判断该下标位置存在的元素的key是否和当前put进来的key是否相等
// 如果相等,则再后续代码中更新value,并返回oldValue
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如果该下标位置存在的元素的类型是TreeNode,表示该位置存的是一颗红黑树
// 那么就会把新元素添加到红黑树中,并且也会判断新key是否已经存在红黑树中
// 如果存在则返回该TreeNode,并在后续代码中更新value
else if (p instanceof TreeNode)
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
else {
// 否则该位置存的是一个链表,那就要把新元素插入到链表中
// 因为要看当前链表的长度,所以就需要遍历链表
// 在遍历链表的过程中,一边记录链表上的元素个数,一边判断是否存在相同的key
// 遍历到尾节点后,将新元素封装为Node对象并插入到链表的尾部
// 并且链表上的元素个数如果已经有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;
}
}
// 如果key存在相同的,则更新value,并返回oldValue
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;
}
// resize方法源码
final Node<K, V>[] resize() {
// resize()包括数组初始化和扩容
// 记录当前数组信息
Node<K, V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
// 计算新数组的数组大小、扩容阈值
int newCap, newThr = 0;
// 如果老数组大小大于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;
splite方法源码
final void split (HashMap < K, V > map, Node < K, V >[]tab,int index, int bit){
TreeNode<K, V> b = this;
// Relink into lo and hi lists, preserving order
TreeNode<K, V> loHead = null, loTail = null;
TreeNode<K, V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
// 由于红黑树是有链表改造而成,所以链表其实还是存在的
// 对链表进行高低拆分
for (TreeNode<K, V> e = b, next; e != null; e = next) {
next = (TreeNode<K, V>) e.next;
e.next = null;
if ((e.hash & bit) == 0) {
if ((e.prev = loTail) == null)
loHead = e;
else
loTail.next = e;
loTail = e;
++lc;
} else {
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
// 拆分之后,如果存在低位链表,则看链表长度,如果小于等于UNTREEIFY_THRESHOLD
// 则把节点类型改为Node类型
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);
else {
// 否则,把头结点转移到新节点(红黑树的根节点一定是链表的头节点)
tab[index] = loHead;
// 如果存在高位链表,
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);
}
}
// 和上面类似
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
treeifyBin方法源码
final void treeifyBin (Node < K, V >[]tab,int hash){
int n, index;
Node<K, V> e;
// 数组长度如果小于MIN_TREEIFY_CAPACITY(默认为64),则会扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
// 把链表改造为双向链表,并且把节点类型改为TreeNode
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);
}
}
}