HashMap作为最常用集合框架,我们应该知道它的组成部分是什么,使用场景是什么,这样我们才能更加合适地使用它
大家都知道(顶级)接口Map的数据结构是key:value,而HashMap作为Map的子类,自然也是以键值对的形式存在。接下来,让我们来看看hashmap里面有什么我们需要注意的地方
基础知识
- 集成自AbstractMap,实现了Cloneable,Serializable接口。 那么就有Object.clone的对象拷贝、还有序列化的能力
public class HashMap<K,V> extends AbstractMap<K,V]]> implements Map<K,V>, Cloneable, Serializable
- 默认容量大小2的4次方 -> 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
- 最大容量 2的30次方≈ 10.7亿
static final int MAXIMUM_CAPACITY = 1 << 30;
- 加载因子默认0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
- 树化阈值,hashmap在数量结构达到一定规模的情况下,会发生结构变化。我们都知道hashmap1.8以后引入了红黑树的概念,这玩意就跟红黑树有关哈
static final int TREEIFY_THRESHOLD = 8;
- 取消树化阈值,当长度小于6时,树形结构将转变为链表结构
static final int UNTREEIFY_THRESHOLD = 6;
- 最小树形化容量阈值,即哈希表中的容量大于64时,才允进行树形化
static final int MIN_TREEIFY_CAPACITY = 64;
- Hashmap数据如何存储?
先下一个定论, hashmap是一个数组+链表的存储结构。
首先说说数组体现在哪里:hashmap底层存储的是Node<K,V>[] 结构,当调用.put(key,value),首先会根据key计算出一个hash值(这里我们暂时先称之为hash),数组当前容量假设为capacity
那么Node<K,V>[]的存储位置计算方式为:
i = (capacity - 1) & hash
tab[i] = new Node(key,value);
/**
* 可以参考一下代码
*/
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;
...
}
Node存储了下一个Node元素的位置,来看看图片长什么样
再看看程序中是如何计算的
// 贴部分源码哈,地方不够凑合看
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的长度。再大就会大于树化阈值,变成红黑树。