文章目录
分析JDK版本:15.0.2
new HashMap<>(); 初始化过程
- 默认无参数构造器
/**
* Constructs an empty {@code HashMap} with the default initial capacity
* (16) and the default load factor (0.75).
* 默认初始化容器大小为 16 默认初始化 loadFactor 为 0.75
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
首次 hashMap.put(“key”, “value”); 创建空间过程
- 默认调用下面的 put 函数
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
- hash(key) 函数如下
// 先拿到 Object 的 hashCode 值
// >>> 无符号右移 16位 高位补 0
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- 最后都调用到了 putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
// 首次put resize() 方法 下面有介绍 resize 首次创建大小为 12 (0.75 * 16)
n = (tab = resize()).length;
// 然后 保证 tab 不为null以后 从 tab 中取当前要放入的数组位置是否已经有数据,如果没有数据会走if (首次一定为null)
if ((p = tab[i = (n - 1) & hash]) == null)
// 直接给 tab 的 i = (n - 1) & hash]位置 放入需要插入的值。
tab[i] = newNode(hash, key, value, null);
else {
// 如果此时数组的位置上已经有 Node 了,那么将会往链表后面插入数据
Node<K,V> e; K k;
// 如果当前位置的Node 的hash 值和将要插入的 keuy的hash值和key做对比 ; 如果 hash相等并且key也相等,那么直接把当前位置的 key 对应的 value 替换为新的value就可以了。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
// 如果目前是树结构;(1.8以后链表大小超过 TREEIFY_THRESHOLD-1 也就是7 则转换为红黑树)
// 下面会介绍红黑树的 putTreeVal
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 如果是插入的新值 启动一个 for(;;) 无限循环
for (int binCount = 0; ; ++binCount) {
// p 是上面 if 里面赋值的当前 hash位置上的值 p = tab[i = (n - 1) & hash];(实际上就是 P 为当前数组位置对应链表的首个值)
// 判断 p 是不是链表的最后一个节点;
// 并且给 e 不断赋值;
if ((e = p.next) == null) {
// 如果 P 是当前链表的最后一个节点;则new 一个 Node 放在当前节点后面;
p.next = newNode(hash, key, value, null);
// 如果链表增加之后 链表的长度 >= 7 则将数组转换为 红黑树 下面会有介绍
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))))
// 如果不是链表最后一个节点;判断循环到的key和hash相等,直接终止循环
break;
// 将 e (e 为上面循if里面赋值的 p.next) 赋值给 p,保证下次循环能拿到下一个 p.next() 实际上就是 p = p.next()
p = e;
}
}
// 上面else里面如果 是在 if(e = p.next) == null) 里面break终止的循环,则 e 此时为null,如果是在下面的if break的循环,此时 e != null
if (e != null) { // existing mapping for key
// 拿到 e 的 value
V oldValue = e.value;
// onlyIfAbsent 默认为false 所以默认一定会走 e.value = value; 否则 如果onlyIfAbsent初始化的时候传入的是 true 那么 只有 oldValue == null 的时候做赋值操作;
// onlyIfAbsent 的作用就是hashMap只重新赋值 key 映射到的 value为null 的值。
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// afterNodeAccess 这个HashMap没有做实现;LinkedHashMap 对这个方法做了实现,将插入的值对应的节点移动到最后
afterNodeAccess(e);
// 如果是修改的 oldValue 并没有新增数据,则没有必要去走下面的数量计数 和 扩容判断等操作
return oldValue;
}
}
// modCount的作用是为了防止并发操作的计数器;例如:如果你在 forEach 的时候去对Map做增删操作的时候 会抛出 throw new ConcurrentModificationException(); 异常就是靠这个参数做判断的。
++modCount;
// 如果增加后的 size > 上一次扩容后的 threshold 值
if (++size > threshold)
// 进行扩容操作;下面有扩容的介绍
resize();
//这个方法 HashMap也没有实现 LinkedHashMap 实现的方法。
afterNodeInsertion(evict);
return null;
}
- 上段代码:首次 putValue ,当 tab = table) == null 的时候;执行了 tab = resize() 方法;这是首次创建空间
// 这段代码只首次调用 resize 分配空间。
final Node<K,V>[] resize() {
// 拿到最新的 table
Node<K,V>[] oldTab = table;
// 初次 oldCap = 0;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// threshold 初次为 0 如果初始化不走默认无参数构造器,默认会赋值;
int oldThr = threshold;
int newCap, newThr = 0;
// oldCap > 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
}
// 这个参数首次 采用 无参数构造器创建的 HashMap对象 默认也是0
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else {
// 所以首次 new HashMap<>(); 走的这个else分支;
// zero initial threshold signifies using defaults
// 默认为 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
newCap = DEFAULT_INITIAL_CAPACITY;
// static final float DEFAULT_LOAD_FACTOR = 0.75f;
// newThr 默认大小为:0.75 * 16 = 12
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 首次为 12 所以这个 if 不走
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
// 首次确定阈值为 newThr 也就是 12;如果不是首次会通过原始数组大小 和 是否需要扩容来判断大小
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
// 创建一个大小为 12 的 Node 数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
// 为 table 赋值,赋值之后下次再put 时的 oldTab 就是这个对象了;Node是每个散列之后的链表对象
table = newTab;
// 因为首次 oldTab == null 所以下面代码首次不走;下面有非首次 resize() 的代码解释
if (oldTab != null) {
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;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
resize() 总结首次调用: putValue 时,会默认创建阈值为12的 Node[]数组。
非首次调用 hashMap.put(“key”, “value”); 插入值和扩容的逻辑
- 非首次调用 put 方法,putVal() 函数还是上面的那个函数,上面已经大体解释清楚,这里主要解释调用 resize() 时需要扩容的情况。
- 下面是 resize() 方法,接下来主要讲述扩容的过程
final Node<K,V>[] resize() {
// 非首次插入调用 resize() 时,table一定不是空的
Node<K,V>[] oldTab = table;
// oldCap 记录的是 oldTab 长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// threshold 上面有记录,首次 resize() 的时候进行赋值,首次赋值时 0.75 * 16 = 12;所以 oldThr = 12;
// 先看看 threshold的解释:The next size value at which to resize (capacity * load factor).
int oldThr = threshold;
// 定义变量 记录新的 容量和大小
int newCap, newThr = 0;
// oldCap > 0 非首次时 true 所以会走 if() 的代码
if (oldCap > 0) {
// 判断旧容量是否超过了最大值 1 << 30
if (oldCap >= MAXIMUM_CAPACITY) {
// 如果超过了最大值则大小设置为 Integer.MAX_VALUE 直接返回了 意思就是不能继续扩容了,一般不会超过的;
// 也是因为下面的扩容规则,每次扩容都需要 << 1 所以超过了 MAXIMUM_CAPACITY 再<< 1 就超过了 Int的最大值了。
// MAXIMUM_CAPACITY = 1073741824 Integer.MAX_VALUE = 2147483647
// MAXIMUM_CAPACITY << 1 = 2147483648 刚好超过了 Integer.MAX_VALUE
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 当 oldCap < MAXIMUM_CAPACITY 的时候 并且上次初始化的值(oldCap >= DEFAULT_INITIAL_CAPACITY)16
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 扩容到之前的 2 倍
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
// 首次初始化传入了 threshold 值的时候走的,第二次不走
newCap = oldThr;
else { // zero initial threshold signifies using defaults
// 这个也是首次无参数构造器逻辑,第二次扩容不走
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// if 条件不满足
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<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
// 赋值全局变量 table 为 newTab
table = newTab;
// 第二次扩容的话 oldTab 一定不为 null
if (oldTab != null) {
// 下面就是扩容后将 oldTab 数据 复制到 newTab中
// 首先循环 oldCap
for (int j = 0; j < oldCap; ++j) {
// 定义临时变量,拿到每次循环对应的 Node 节点;
Node<K,V> e;
// 拿到 数组的第 j 个节点的 Node 赋值给 e ;如果 == null 的话代表 当前数组位还没有值
if ((e = oldTab[j]) != null) {
// 将原数组的当前值清空
oldTab[j] = null;
// 判断当前数组位的链表是否有下一个值
if (e.next == null)
// 如果链表只有一个值,则扩容后的容器通过 e.hash & (newCap - 1) 运算把取出的 e 放到对应的位置
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
// 如果取出来的是是 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 {
// 拿到当前 Node 的下一个 Node
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 循环到链表的最后一个节点为止
} while ((e = next) != null);
// 不需要移动的节点直接的头节点放在 原来 J 的位置
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// 需要移动的节点的头节点 放到对应的位置
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
分析 get 流程:hashMap.get(“key”);
public V get(Object key) {
Node<K,V> e;
return (e = getNode(key)) == null ? null : e.value;
}
- 重点是看 getNode(key) 方法如何获取的 Node 节点。
final Node<K,V> getNode(Object key) {
Node<K,V>[] tab;
Node<K,V> first, e;
int n, hash;
K k;
if (
// 拿到当前 table 赋值给 tab
(tab = table) != null
// 记录 table 的长度
&& (n = tab.length) > 0
// 通过 (n - 1) & (hash = hash(key)) 位运算去匹配当前key 对应到了桶的哪个位置,然后取出对应位置的 head 节点
&& (first = tab[(n - 1) & (hash = hash(key))]) != null) {
// 用取出的 Node 存储的hash对比传入的 hash和key 相等则返回
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 取出next节点
if ((e = first.next) != null) {
// 如果是 tree 结构,从 Tree中读取
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
// 取出的 Node key 和 key的hash 都相等 则返回
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
// while继续取下一个 Node...
} while ((e = e.next) != null);
}
}
return null;
}
分析 remove 流程:hashMap.remove(“key”);
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
- 看 removeNode() 方法
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
// 还是先拿到 table 赋值给 tab;tab的长度赋值给 n
// p 为 通过需要remove的key的hash 拿到对应桶节点的链表的 head 如果head为null 则代表需要移除的key没有对应值
if ((tab = table) != null
&& (n = tab.length) > 0
// 通过 Hash 计算当前要操作的 key 在桶的哪个位置 ,如果存在则将链表head节点赋值给 p
&&(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
// 判断如果当前拿出的p 是否是要操作的节点
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 如果head就是要找的数据 则 直接赋值给node
node = p;
else if ((e = p.next) != null) {
// 如果head不是要找的节点,则取出 p.next 下一个节点继续做 do while{} 循环查找
// 如果是 tree 则去 getTreeNode 查找
if (p instanceof TreeNode)
// 如果是 tree 结构,去 TreeNode 中查找对应的 Node
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
// 否则循环查找 next
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
// 如果查找到了对应节点 matchValue 默认是true则需要对比value相等,如果传入false则不对比value 只要key相等就直接移除
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
// 从tree中移除
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
// 这个 else if 判断 要移除的 node 是否是 第一个元素
else if (node == p)
// index是方法开始的时候赋值的 对应key在数组的第几个位置。
// 这里是将要移除的链表节点的下一个节点放到对应的index位置作为替换
tab[index] = node.next;
else
// 如果要移除的元素既不是 tree 也不是 head节点 那么将 node.next 前移一位
p.next = node.next;
// 记录的是对 HashMap 的操作次数 上文有提到
++modCount;
// 移除一位以后对应大小 -1
--size;
// hashMap 未实现,具体分析 LinkedHashMap 的时候需要分析
afterNodeRemoval(node);
return node;
}
}
return null;
}
往红黑树中插入值
- ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
- 为什么采用红黑树,感觉是因为红黑树虽然查找没有 AVLTree 查找效率高,但是也不像 AVLTree 那样需要频繁调整树的结构来保证平衡。是性能比较均衡的。
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) {
// h 是 要存入的 hash值 k 是 要存入的的key。value 要存入的 value
Class<?> kc = null;
boolean searched = false;
// 先查找到树的根节点 因为是从数组 > 7 到达8 的时候转换为红黑树 ,所以根节点一定是有的
TreeNode<K,V> root = (parent != null) ? root() : this;
// 从根节点向下搜索
for (TreeNode<K,V> p = root;;) {
// dir 记录 -1 和 1 位了记录是插入到左子节点还是有子节点
int dir, ph; K pk;
// 循环 判断当前要存储的 hash 值是否和节点的 hash 大小
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
// 如果当前循环到的节点就是要找的 直接 return 节点返回
return p;
else if ((kc == null &&
// comparableClassFor 方法查找节点实现的排序接口
(kc = comparableClassFor(k)) == null) ||
// compareComparables 是通过排序接口进行两个节点的比较
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
return q;
}
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p;
// 通过目标 hash 判断出的 dir 是 0 还是 -1 来继续确定往左侧子节点还是右侧 如果确定的子节点 == null 则直接newTreeNode
// 如果 p = (dir <= 0) ? p.left : p.right != null 的话 需要继续for循环判断下一个 子节点的逻辑,
// 直到最后 (p = (dir <= 0) ? p.left : p.right) == null 才插入到对应子节点上
if ((p = (dir <= 0) ? p.left : p.right) == null) {
Node<K,V> xpn = xp.next;
// 创建新 TreeNode
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
// 通过 dir 判断是左还是右 然后给当前节点的左或者右节点赋值
if (dir <= 0)
xp.left = x;
else
xp.right = x;
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x;
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}
将链表转换为红黑树:treeifyBin(tab, hash);
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
// 如果表太小也将扩容
resize();
// 查找到 Node 通过 (n - 1) & hash 计算出在数组中的位置
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
// 将 Node的数据结构 替换为 TreeNode 的数据结构
TreeNode<K,V> p = replacementTreeNode(e, null);
// 通过 do while 循环 每次把 Node 转换成 TreeNode后 做对应节点的关联
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
// 从当前Node开始的 tree
hd.treeify(tab);
}
}
从红黑树中读取操作
- ((TreeNode<K,V>)first).getTreeNode(hash, key);
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
TreeNode<K,V> p = this;
do {
int ph, dir; K pk;
TreeNode<K,V> pl = p.left, pr = p.right, q;
// 先确认是 left 还是 right
if ((ph = p.hash) > h)
p = pl;
else if (ph < h)
p = pr;
// 如果相等则直接返回p
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
// 左子为 null 往右查找
else if (pl == null)
p = pr;
// 右子为null 往左查找
else if (pr == null)
p = pl;
// kc 正常传入的是 null
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
// 通过递归朝着下一个子节点查找
else if ((q = pr.find(h, k, kc)) != null)
//递归找到返回
return q;
else
p = pl;
} while (p != null);
return null;
}
HashMap 常被问到的问题
- getNode() 的时候,(n - 1) & (hash = hash(key)) 为什么可以通过key对应匹配到桶的相对位置
实际上确定数据在桶的位置就是通过 putValue 中有一句代码;所以取数据也是通过这种方式
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
- resize() 的时候 (e.hash & oldCap) == 0 这行代码为什么可以判断扩容后原位置的链表是否需要移动位置(这个问题实际上就是为什么要求数组是2的倍数)
具体原因看图(主要是 n-1 从 0…0111 变为 0…1111)第n+1位是否为1 相当于多一位进行位运算
- 什么是哈希碰撞 ,怎么解决的
(什么是哈希函数:https://www.bilibili.com/video/BV1Za411r7LR?spm_id_from=333.999.0.0)
大概思想是:像这样 int out = f(“in”) 一个获取hash的函数,实际上可能的输入 “in” 为无穷多个;但是输出值 out 理论上并不是无穷多,所以一个无穷的输入对比一个有穷尽的返回值来说,一定有不同的 in 对应 相同的 out ;也就是 hash冲突了
- HashMap 中的hash冲突
Hash冲突有 (开放定址法)(拉链法) 等,HashMap中采用的拉链法,将碰撞的 Node 存储在链表下一位。(所以会发现源码中获取值的时候都是先判断hash 然后判断key的eques方法)
- 查找的时候 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) 为什么判断了hash还需要判断 key 的值:
因为 Hash冲突 多一个key 可能hash后返回同一个 hash 值。
- newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 为什么扩容的时候需要乘以DEFAULT_LOAD_FACTOR(0.75)
- 使用 HashMap 高并发可能会发生什么?
首先并发会把Map对象读取到线程内存
1:如果两个线程同时修改插入数据,且两条数据恰巧发生了碰撞(或者修改了同一个值),如果当前桶位置的Node链表为 A->B 如果两个线程同时修改B的next节点则一定会丢失一个。(特殊情况还有可能使链表形成环,第一个线程修改成 A->B 第二个线程修改成 B->A)
如果发生了环,则有可能会死循环;2:如果并发的情况下进行了扩容操作:对Node的位置发生了顺序变化,也可能出现环。
- 为什么不直接将key作为哈希值而是与高16位做异或运算?
目的:为了增加随机性,散列充分,减少碰撞。即使很相近的连个值如 wojiushiwo01 wojiushiwo02 hash之后相差也很大。
- HashMap加载因子为什么是0.75?
作为一般规则,默认负载因子(0.75)在时间和空间成本上提供了很好的折衷。较高的值会降低空间开销,但提高查找成本(体现在大多数的HashMap类的操作,包括get和put)。设置初始大小时,应该考虑预计的entry数在map及其负载系数,并且尽量减少rehash操作的次数。如果初始容量大于最大条目数除以负载因子,rehash操作将不会发生。
(泊松分布问题…请百度)