|
@@ -122,3 +122,118 @@ if (++size > threshold)
|
|
if (++size > threshold)
|
|
if (++size > threshold)
|
|
resize();
|
|
resize();
|
|
|
|
|
|
|
|
+## 分析hashmap扩容原理
|
|
|
|
+
|
|
|
|
+扩容的时候以2的幂次方扩容,将原来的table中存放的key循环遍历存入新的table中
|
|
|
|
+误区:不会重新计算hash值,重新计算index(为什么链表会缓存hash值)
|
|
|
|
+
|
|
|
|
+### 1.7版本的扩容
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+void transfer(Entry[] newTable, boolean rehash) {
|
|
|
|
+ int newCapacity = newTable.length;
|
|
|
|
+ // 遍历原来宿主中所有的链表
|
|
|
|
+ for (Entry<K,V> e : table) {
|
|
|
|
+ // 如果存在链表
|
|
|
|
+ while(null != e) {
|
|
|
|
+ Entry<K,V> next = e.next;
|
|
|
|
+ // 是否需要重新计算hash
|
|
|
|
+ if (rehash) {
|
|
|
|
+ e.hash = null == e.key ? 0 : hash(e.key);
|
|
|
|
+ }
|
|
|
|
+ // 通过key值的hash值和新数组的大小算出在当前数组中的存放位置
|
|
|
|
+ int i = indexFor(e.hash, newCapacity);
|
|
|
|
+ // 会发生死循环
|
|
|
|
+ // 这里使用了头插法
|
|
|
|
+ e.next = newTable[i];
|
|
|
|
+ newTable[i] = e;
|
|
|
|
+ e = next;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+hashmap1.7 16×2=32
|
|
|
|
+hashmap1.8 16<<1=32
|
|
|
|
+
|
|
|
|
+线程安全问题,多线程访问共享全局变量
|
|
|
|
+什么情况下产生死循环?
|
|
|
|
+因为table多线程共享,newTable是线程私有的。会导致C->A->C
|
|
|
|
+如果查询当前不存在的值会导致死循环,导致CPU飙升。
|
|
|
|
+为什么JDK官方不承认JDK1.7的BUG?
|
|
|
|
+多线程才会引起BUG,单线程不会有BUG
|
|
|
|
+多线程推荐使用并发的多线程hashmap
|
|
|
|
+
|
|
|
|
+### 1.8版本的扩容
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+// 查找所有链表
|
|
|
|
+for (int j = 0; j < oldCap; ++j) {
|
|
|
|
+ Node<K,V> e;
|
|
|
|
+ // 如果有链表
|
|
|
|
+ if ((e = oldTab[j]) != null) {
|
|
|
|
+ // 清空原来的链表,也为了避免死循环
|
|
|
|
+ oldTab[j] = null;
|
|
|
|
+ // 判断链表是否到底部
|
|
|
|
+ // 新的值肯定比旧值大
|
|
|
|
+ if (e.next == null)
|
|
|
|
+ newTab[e.hash & (newCap - 1)] = e;
|
|
|
|
+ // 判断当前链表是否为红黑树
|
|
|
|
+ else if (e instanceof TreeNode)
|
|
|
|
+ ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
|
|
|
|
+ else { // preserve order
|
|
|
|
+ // 如果为链表的情况下
|
|
|
|
+ // 低位链表
|
|
|
|
+ Node<K,V> loHead = null, loTail = null;
|
|
|
|
+ // 高位链表
|
|
|
|
+ Node<K,V> hiHead = null, hiTail = null;
|
|
|
|
+ Node<K,V> 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;
|
|
|
|
+ }
|
|
|
|
+ // 这里肯定不会有冲突
|
|
|
|
+ // 新的值一定比旧的值大oldCap
|
|
|
|
+ if (hiTail != null) {
|
|
|
|
+ hiTail.next = null;
|
|
|
|
+ newTab[j + oldCap] = hiHead;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+1.8解决死循环问题?
|
|
|
|
+将一个链表通过与运算拆分成两个链表存放在新的table中。
|
|
|
|
+
|
|
|
|
+## 为什么加载因子不是1,而是0.75?
|
|
|
|
+
|
|
|
|
+如果加载因子越大,空间利用率越高,冲突发生概率较大。反之,加载因子越小,空间利用率越低,冲突发生概率较小。综合泊松分布,得出0.75效率最高
|
|
|
|
+
|
|
|
|
+## 如何存放一万条key效率最高?
|
|
|
|
+
|
|
|
|
+会触发10次扩容!
|
|
|
|
+推荐指定hashmap集合的初始化容量,(initialCapacity) = 存储元素个数 / 加载因子 + 1
|
|
|
|
+
|
|
|
|
+## hashmap7与8的区别
|
|
|
|
+
|
|
|
|
+1. 1.7基于数组加链表,采用头插法
|
|
|
|
+2. 1.8基于数组加链表加红黑树,采用尾插法
|
|
|
|
+ A. 能够降低key对应的index冲突概率,提高查询概率
|
|
|
|
+ B. 会拆分链表
|