Q: hashMap的初始容量为什么时16.
A: DEFAULT_INITIAL_CAPACITY为16, hash因用位运算。特定2n。所以map容量需要是2n。而16应该是比较合适的大小。
MAXIMUM_CAPACITY最大数组元素数量,2^30。
DEFAULT_LOAD_FACTOR 负载因子。默认为0.75
Q: hash?
A:(hashcode ^ hashcode<<<16) & (n-1)
获取小于n-1 低位掩码。
为了减少冲突。 高低位异或,让高位参与运算。
Q: 扩容条件?
A:
1.7: 1、 存放新值的时候当前已有元素的个数必须大于等于阈值
2、 存放新值的时候当前存放数据发生hash碰撞(当前key计算的hash值换算出来的数组下标位置已经存在值)
1.8:a 当前存入数据大于阈值即发生扩容
b 存入数据到某一条链表上,此时数据大于8,且总数量小于64即发生扩容
Q: 为什么不是1 或者 0.5?
当负载因子为1时,将数组填充满才会发生扩容,虽然提高了空间利用率,但会出现很多链表已经形成红黑树的情况。会增加查询时间增大。
当负载因子为0.5时,当hash后的值超过length/2.发生扩容,频繁的扩容使耗时降低,但有相当多的空间浪费。
负载因子的值时在hashCode分布较好时,降低tree结构产生。当负载因子为0.75时,桶中节点的分布频率服从参数为0.5的泊松分布。
What is the significance of load factor in HashMap?
计算0.5的泊松分布。
Q:为什么HashMap桶中元素个数大于8转换成红黑树?
A: TreeNode占用空间是Node的两倍。所以尽量不希望Node变成TreeNode。而随着链表越来越长。查询时间复杂度为O(n),需要降低耗时,将Node转换成TreeNode。查询时间复杂度为O(log(n)). hash碰撞观察8层节点个概率为0.00000006。
Q: 为什么HashMap树的退化的阈值是6?
A:首先退化阈值从8递减。 阈值为8的情况:建树与退化循环进行。阈值为7的情况:当树中有8个节点时,删除一个节点就进行退化,增加一个节点就进行建树。频繁的变换形态会浪费大量的时间,降低性能。
Q:有没有大于TREEIFY_THRESHOLD时,不建树的情况?
A: 有!当容量小于MIN_TREEIFY_CAPACITY时,仍然使用链表形式存储。
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)// 初始化数组
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) //当前桶无节点创建节点
tab[i] = newNode(hash, key, value, null);
else {// hash冲突
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))//k与节点相同
e = p;
else if (p instanceof TreeNode)//如果节点是TreeNode将value put进去
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 (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 阈值大于7,建树
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))//与put中key的hash值相同,跳出
break;
p = e;
}
}
if (e != null) { // 如果存在key更新value
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)// 重散列
resize();
afterNodeInsertion(evict);
return null;
}
Q: 说下put?
A: 1.如果未put,首先初始化数组。2.当前桶无元素,新建节点。3.1 当前节点key与put的key相同获取节点。3.2 当前node是treeNode,putTreeVal 3.3 循环链表,找到最后一个节点或者与put的key相同的节点并获取。4. 更新节点。5. 若是size大于阈值,重散列。
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {// 表不为空,桶不为空
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))//桶中第一个是否匹配
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)//是否为树结构
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);//循环链表获取节点
}
}
return null;
}
Q:说下get?
A:1.判断表、桶是否为空。2. 判断桶中第一个元素是否匹配。3.判断是否为书结构。4. 循环链表或者查找树获取节点。
Q:hashMap有什么线程安全问题?
A:1.7 会有成环问题。因为put时先扩容再插入。1.8会有数据丢失问题。因为put时先插入后扩容。
Q:hashMap put时,元素的位置?
A: 1.7 插头。1.8插尾。
Q: 红黑树特性。
A: 很节点为黑。子节点为黑色。 如果有其余节点为红色,子节点为黑色。 每个叶子节点到根节点之间的黑色节点个数是相同的。