HashMap put 相关函数
文接上回,咱们讲了HashMap 的结构函数,首要便是设置 负载因子 和 扩容阈值。
这章咱们来看HashMap put 的相关函数,不多bb,上源码:
public V put(K key, V value) {
return putValapple(hash(key), key, value, false, true);
}
@Override
public V putIfAbsent(K key, V value) {
return putVal(hash(key), key函数调用句子, value, true, true);
}
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m,函数调用c言语 true源码共享网);
}
put 相关的函数就这3 个,前两函数调用个都是直接调用的
putVal(hash(key), key, value, true, true),
后面一个应该还有形象,跟参数为Map结构函数调用的是同一个函函数调用进程数putMapEntries()。
接着咱们就看下putVal(h源码编辑器ash(key), k源码编辑器手机版下载ey, value, true, true) 这个函数
putVal(hash(key), key, value, true, true) 函数
transient Node<K,V>[] table;
/**
* 在put 相关办法中被调函数调用c言语用
*
* @param hash hash for key
* @param k函数调用c言语ey the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table函数调用c言语 i函数调用能够出现在表达式中吗s in creation mode.
* @return pjava编译器revious value, or null if nonejava怎样读
*/
final V pu函数调用tVal(int hash, K key, V value, boolean函数调用的三种方式 onlyIfAbsent,
boolean evict) {
Nodjava初学e<K,V>[]链表排序 tab; Node<K,V> p; int njava模拟器, i;
if ((tab = table) == null || (n = tab.length) == 0)//注释1
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash])application == nappearull) //注释2
tab[i] = new链表排序Node(h函数调用句子ash, kjava怎样读ey, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
els链表完结栈e 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 (bin链表不具有的特点是Cojava难学吗unt >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBinjavascript(tab, hash);
breaAPPk;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(函数调用的一般格局k))))
break;
p = e;
}
}
if (e != null) { // existing majava面试题pping for key
V oldValue = e.java编译器value;
if (!onlyIfAbapprovesent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//注释3
if (++size > threshold)
resize();
afterNodeInserti链表的创建on(evict);
return njava面试题ull;
}
第一次调用时
假定咱们源码码头是通过HashMap()
这个结构函数创立的HashMap 目标并第一次调用 put(K key, V value) 函数函数调用进程。
- 咱们先看下No源码时代de 类的结构
static class N链表c言语ode<K,V> implements Map.Entry<K,V&源码编辑器gt; {
final int hash;
fi链表的创建n源码时代al K key;
V value;
Nod函数调用c言语e&app装置下载lt;K,V> next;
Node(int hash, K key, V valu链表完结栈e, Node<K,V> next链表逆序) {
this.happstoreash = hash;
this.key = key;
this.val链表不具有的特点是ue = value;
this.next = next;
}
//省掉代码。。。
}
能够看出这是一个单链表结构,存放着 hash、key和vajava工作培训班l链表排序ue
- resize() 函数,作用:初始化或函数调用的一般格局者扩容表为原大小的2倍。源码后面再剖析。
- 知道以上的信息咱们在看 putVal() 函数的代码,注释1
if ((tab = table) == null || (n = tab.length) == 0)//注释1
n = (tab = resize()).length;
咱们知道DEFAULT_INITIAL_CAPACITY = 1 << 4 // aka 16
因此能够知道tab = (Node<K,V>[])new Node[16]
和 n = 16
- 接着往下看,注释2
if ((p = tab[i = (n - 1) &a函数调用进程mp; hashjava初学]) == null) //注释2
tab[i] = newNode(hash函数调用, key, value, nu链表排序ll)函数调用不能出现在以下哪种状况;
咱们是第一次调用,p = tab[i = (n - 1) & hash]
肯定是null ,所以apple咱们这次就成功的把key,value 存到了tab[i]
中。
- 咱们走进了if,链表数据结构else 的代码就不必看了,直接到了//注释3 的方位
++modCount
用于记录修改的次数,接着往函数调用c言语下看:
if (++size > threshold)
resize();
threshold
为函数调用时的实参和形参之间传递扩容阈值,初始化时为 DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR //aka 16*0.75 = 12
, sizjava工作培训班e 为Ha源码编辑器手机版下载shM源码共享网ap 中保存 Node源码网站的数量,当等于 扩容阈值 时就需求对 tab 进行扩容。
接着往下是 afterNodeInsertion(evict);
这是一个空办法,什么都没做。
void afterNodeInsertion(boolean evict) { }
好了,咱们第一次java难学吗调用就完毕了。成功的把数据存储到了HashMap 中,再回头看下咱们之前没有看的 else 中的状况
else 中的状况
当tab[i = (n - 1) & ha函数调用不能出现在以下哪种状况sh]
中已经有链表完结栈值的状况就会走到 else 中,看代码:
static fin源码编程器al int TREEIFY_THRappointmentESHOLD = 8;
if ((p = tab[i = (n - 1) & hash]) =java怎样读= null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != nu函数调用能够作为一个函数的形参ll源码网站 && key链表.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreappointmenteNo函数调用句子de<K,V>)p).putTreeVal(this, tab, hash, key, v函数调用c言语alue);javascript
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next =链表完结栈 newNode(hash,approve key, value, null);
if (binCount >= TREEIFY_THRapproachESHOLD - 1) // -1 for 1st
treeif函数调用的三种方式yBin(tab, haAPPsh);
break;
}
if (e.hash == hash &&java言语amp;
((k = e.key) == key || (key != null && key函数调用中的参数太少.equals(k))))
b源码共享网reak;
p = e;
}
}
if (e != null) { //existing ma链表结构pping for key //注释4
V oldValuappstoree = e.v源码网alue;
if (!onlyIfAbsent || oldValue == null)
e.value = value源码编辑器手机版下载;
afterNodeAccess(e);
return oldValue;
}
}
-
TreeNJavaode 为红黑树,有爱好的同学能够自行了解 红黑树深化剖析及Java完结
-
咱们看接下链表完结栈来的判别,能够分为3中状况
- p.kjava难学吗ey 与 参数key 相同
直接将p 赋值给 Node e
- p 为 Tr链表结构eeNode
将Java需求保存的内容 增加到红黑树p 中,回来值赋给e
- else
遍里链表p,且成果赋值函数调用的一般格局给e; 也可分为3 中状况
1)源码编辑器编程猫下载.if (e.hash =appstore= hash &&java编译器; ((k = e.key) == key || (key != null && key.equals(k)源码网站))源码编辑器编程猫下载)
当链表中有Node 的key 与参数key 相一起,完毕遍历。
2).if ((e = p.nex源码码头t) == null)
链表遍历完时
将需求保存的内容 增加到链表完毕。假定链表长度小于8,完毕遍历
3). 链表遍历完,且增加新增内容。链表长度大于等于8 时。
实行treeify函数调用Bin(tab, hash)
函数,然后完毕遍历。static final int MIN_Tjava环境变量装备REEIFY_CAPACITY = 64; final void treeifJavayBin(Node<K,V>[] tab, int hash) { int n, index; Node<approveK,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CA函数调用的一般格局PACITY)链表和数组的差异 resize(); elseAPP if ((e = tab[index = (n - 1) & hash]) != null链表和数组的差异) { TreeNode&l函数调用的三种方式t;K,V> hd = null, tl = null; do { TreeNode<K,V&g函数调用不能出现在以下哪种状况t; p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = pappointment; } tl = p; } while ((e = e.next) != null); if (函数调用的三种方式(tab[index] = hd) != null) hd.treeify(tab); } }
treeifyBin(tab, hash)
函数的逻辑是当tab 长度小于64 时就实行resize()
扩容,否则将链表转为红黑树 -
咱们会过来看注释函数调用c言语4 处代码
if (e != null)链表回转 { //当参数key 在tab 中有映射时
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
onlyIfAbsent
为true 时不修改现有值
当参数key 在taapp装置下载b 中有映射时,根据条件掩盖现有值,并回来旧值。
int hash(Object key) 函数
static final int hash(Object key) {
int h;
re函数调用的三种方式turn (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
-
^
假定相对应位值相同函数调用时的实参和形参之间传递,则成果为0,否则为1,&
假定相对应位都是1,则成果为1,否则为0
a = 1010
b = 0011
------------
a ^ b = 1源码码头001
a &链表结构amp;approach b = 0010
-
(h = key.hashCode()) ^ (h >&g源码网站t;函数调用进程> 16)
这个办法最重要的一句代码。为什么要做^ (h &g函数调用的一般格局t;>> 16)
这个操作呢?
是因为在putVal 函数中,是这样是运用的 tab[index = (n - 1) & hash]
,n 是表的长度,表的长度永远都是2函数调用时的实参和形参之间传递的幂次方,那么n-1
的高位应该满是0,做 &
操作时会导致hash 的高位无法参与运算,从而会带来哈希抵触的风险。所以在核算key的哈希值的时分,做(h = key.hashCode()) ^ (h >>> 16)
操作。函数调用时的实参和形参之间传递这也就让高位参与到tab[index = (n - 1) & hash]
的核算中来了,即下降java环境变量装备了哈希抵触appreciate的风险又不会带来太大的功用问题。
resize() 函数
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = t源码码头able;
int oldCap = (oldTab == null) ? 0 : oldTab.l源码编辑器编程猫下载ength;源码资本
int oldThr = tjava模拟器hreshold;
in链表逆序t newCa源码资本p, newThr = 0;
if (oldCap > 0) {//table中已经有数据
if (ol函数调用的三种方式dCap >= MAXIMUM_CAPA链表不具有的特点是CITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// newCap源码网 = oldCap * 2
else if函数调用能够作为一个函数的形参 ((newCap = oldCap << 1) < MAXIMUM_C链表排序APACITY &&
oldCap >= DEFAULT_IN函数调用的一般格局ITIAL_CAPACITY)
newThr = oldThr << 1; //newThr = oldThr * 2
}
else if函数调用进程 (oldThr > 0) //初始化是设置了容源码资本量和阈值 运用非空结构函数初始化
//回忆一下结构函数中 threshold = tableSizeFor(initialCapacity) 值为2的幂次方
//本来这源码个值其实是给newCap 所以需求为2的幂次方
// initial capacity was placed in threshold
newCap = oldThr;
else { //初始化是没有设置容量和阈值, 运用的java模拟器是空的结构函数初始化 zero initial threshold signifies usapp装置下载ing defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACI链表和数组的差异TY);
}
i链表回转f (newThr == 0) {// 上面判别进入 (ol函数调用时的实参和形参之间传递dThr > 0) 的状况,没有给newThr 赋值,
// 所以在这儿给newThr 赋值
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_appleCAPACITY &&链表amp; ft &函数调用的三种方式lt; (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshojava怎样读ld = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table =approve newTab;
if函数调用的一般格局 (oldTab != null) {
for (int j = 0; j < oldCap; ++jappearance) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
o链表数据结构ldTab[j] = njava面试题ull;
if (e函数调用.next == null)//链表只要一个节点的时分
newTab[e.hash & (newCap - 1)] = e;
else ijava怎样读f (e instanc源码编程器eof TreeNode)//节点为红黑树
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve orderappearance
Node<K,V> loHead = null, loTail = null;
Node<K,V&gjava环境变量装备t; hiHead = null, hiTail = null;
Node<K,V> next;
d链表排序o {
next = e.next;
if ((e.hash & oldCap)链表数据结构 == 0) {//注释函数调用能够出现在表达式中吗5
if (l链表逆序oTail == 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;
newT链表逆序ab[j] = loHead;//注释6
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiJavaHead;//注释7
}
}
}
}
}
return newTab;
}
r链表回转esize() 函数能够appear分为两个部分
- 设置 newCap、newThr。剖析已经写在了代码中,逻辑便是:没有初始化的approve状况初始化、已经有函数调用中的参数太少值的乘以2
- 创立newTab 并从头赋值。这儿咱们关键解说一下
if ((e.hash & ol源码码头dCap)链表c言语 == 0)
注释5 处的代码。
- 首要咱们知道
tab[index = (n - 1) & hash]
,假定oldCap = 16 时
oldCap-1 = ... 0000 1111
hash1 = ... 0000 0101 -> ind链表完结栈ex1 = 0000 0101 = 5
hash2 = ... 0001 0101 -> index2 = 0000 0101 = 5
- 此刻需求扩容 newCap = oldCap*2 = 32
newCap-1 =函数调用能够作为一个函数的形参 ... 0001 1111
has源码共享网h1 = ... 0000 0101 -> index1 = 0000 0101 = 5
hash2Java = ... 0001 0101 -> in函数调用不能出现在以下哪种状况dex2 = 0001 0101 = 5 + 16(oldCap)
- 从以上两步能够看出,在扩容的时分并不是一切index 都会改动的。而改动的关键便是在 hash 值在 oldCap 的位上是否为0。
if ((e.java怎样读hash &am源码码头p; oldCap) == 0)
注释5 处的代码就类似于一下列子。作用便是判别是否需求位移。
oldCap = ... 0001 0000
hash1 = ... 0000 0101 -> 0
hash2 =java模拟器 ... 0001 0101 -> 1
- 这也解说了注释6、注释7处的代码
小结
这章咱们首链表完结栈要涉及到3个函数
- putVal(…)
该函数担任数据刺进,当size 超越扩容阈值时调用resiappointmentze()
函数扩容,当增加数据的链表长度大于等于8 时将链表转为红黑树。
- hash(Obj函数调用句子ect key)
在核算ke链表y的哈希值的时分,用其本身hashcode值与其低16位做异或操作。这也就让高位参与到index的核算中来了,即appreciate下降了哈希抵触的风险又不会带来太大的功用问题。
- resize()
从头设置table 的容量和扩容阈值,并新建ta链表c言语ble 把oldTable 的值填充进去。