HashMap 内的主要数据结构
- 内部类 Node<K,V>(实现了Map.entry接口,存储key-value的基础类,链表)
- table (Node<K,V> 数组)
基本思路是(后续会做更详细的解读):
- 根据key的hash值确定table的index索引
- table的每个index实际上存储的是Node链表,由第1步确定索引后,再循环链表如果存在相同key(key.equals)则替换Node的value值,否则链表尾添加新Node
构造函数
无参构造函数
threshold 变量为阈值,table大小查过该变量就会触发扩容(resize)
而 threshold = capacity * loadFactor
public HashMap() {
// 默认扩容因子0.75
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
// table 的初始化推迟到了 map.put(key,value) 的resize()时
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
/** 省略部分代码 **/
// 初始化table
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
Map参数构造函数
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;// 默认扩容因子0.75
//主要做两件事情
//1. 利用上一篇文章提到的 求2的幂 的方法,确定HashMap的size
//2. 将 m 内的key-value(即node)逐个复制到该HashMap中
putMapEntries(m, false);
}
初始变量构造函数
public HashMap(int initialCapacity, float loadFactor) {
// 参数的合法性校验
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
// threshold 为扩容阈值,超过该值即触发resize
// tableSizeFor即上篇文章介绍的求2的幂方法
this.threshold = tableSizeFor(initialCapacity);
}
// table 的初始化也推迟到了 map.put(key,value) 的resize()时,与无参构造函数不同的是,此处初始化的大小,正是你设置的initialCapacity,而threshold也被重新计算
// 利用threshold做一个存储的过渡,完美做到了一点都不浪费不啰嗦的优良传统
newCap = oldThr;
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
// 阿里开发规约建议使用该 初始化函数,设置初始容量,尽可能的避免map.resize带来的性能消耗,也提高存储空间的利用率(用多少声明多少)
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
小结
综上可以发现,初始化主要是设置table容量相关值,具体的table大小初始化推迟到了putValue阶段,这也就是下篇文章要介绍的重点。