HashMap作为最常用集合框架,我们应该知道它的组成部分是什么,使用场景是什么,这样我们才能更加合适地使用它
大家都知道(顶级)接口Map的数据结构是key:value,而HashMap作为Map的子类,自然也是以键值对的形式存在。接下来,让我们来看看hashmap里面有什么我们需要注意的地方
基础知识
public class HashMap<K,V> extends AbstractMap<K,V]]> implements Map<K,V>, Cloneable, Serializable
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
/**
* 可以参考一下代码
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
...
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
...
return null;
}
此时存储另一个元素.put(key1,value1)
假如key1通过hash计算出来的结果跟key的hash值一样,那么势必它们最后计算出来的存储位置会一样,都为3.
假设key1为节点node1,key0为节点node0。(key1!=key0)
node1不会存储在Node[]数组中,而是追加到了node0节点后面形成链表。
看下Node的数据结构
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
...
}
再看看程序中是如何计算的
// 贴部分源码哈,地方不够凑合看
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
...省略
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;
}
...省略
}
稍微做点解释,p为Node[]数组中的节点,即本案例的node0,新加入的节点node1会通过这行代码追加到node0的下个节点,形成链表
p.next = newNode(hash, key, value, null);
// 对应本案例的
Node node1 = newNode(hash,key,value,null);
node0.next = node1;
注意一下这行代码哈treeifyBin(tab, hash);
默认的,我们都知道当node链表的数量大于8时,链表会转化成红黑树,但是还有一个条件不要忘记,就是数组的长度要大于MIN_TREEIFY_CAPACITY 最小数化阈值,否则node数组只会resize。 理论上,假如所有存储的key计算出来的hash值都相同,那么链表长度最大可以到达 MIN_TREEIFY_CAPACITY-1,也就是63的长度。再大就会大于树化阈值,变成红黑树。