一、HashMap源码概览
I. 可以结合JDK源码包了解java.util.HashMap实现原理
II. 继承抽象类java.util.AbstractMap,实现了三个接口,分别是:java.util.Map、java.lang.Cloneble、 java.io.Serializable
III. 主要的成员变量
编号 | 变量代码 | 变量注释 |
1 | static final int DEFAULT_INITIAL_CAPACITY = 16; | 默认的初始化容量大小为16,可以通过HashMap(int initialCapacity) 或者HashMap(int initialCapacity,float loadFactor)这两个构造函数来改变此容量大小,但是实际的容量值可能与你设定的值不同(具体操作见下面解释) |
2 | static final int MAXIMUM_CAPACITY = 1 << 30; | 最大的容量值,1左移30位,即2³⁰ |
3 | static final float DEFAULT_LOAD_FACTOR = 0.75f; | 默认的加载因子(作用见下面解释) |
4 | transient Entry[] table; | Entry数组, HashMap内部存储实际就是Entry对象数组(Entry详细解释见下面解释,关键字transient的作用见我另一博客说明, 点击打开链接 ). 此数组如果需要的话,大小可以被重置,但是其长度要一直是2的幂次方 |
5 | transient int size; | 此Map的大小(即k-v映射的长度) |
6 | int threshold; | Map准备扩容的临界阀值(临界阀值的计算方法见下面的解释),即在Map在使用过程中,如果发现Map使用量(即keys大小)已经超过此阀值,则会调用resize()方法进行重置大小(会进行再哈希,将旧数据交换到扩容后的Map容器中,详细步骤见下面解释) |
7 | final float loadFactor; | 加载因子,默认值为此表格中编号3的变量对应的值,即0.75,可以通过构造HashMap(int initialCapacity, float loadFactor) 来改变此值 |
8 | transient volatile int modCount; |
VI.构造方法
i. 默认总共有四个构造函数,归根到底可以总结只有一个构造,下面将逐一介绍
ii. 构造一:HashMap(int initialCapacity,float loadFactor),源码以及分析如下:
/**
* 通过给定的容量值和加载因子来构造一个空的 HashMap
* 但是指定的这两个参数值的范围是有要求的,具体看下面代码段的分析
*
* @param initialCapacity 给定的初始化容量
* @param loadFactor 给定的负载因子
* @throws IllegalArgumentException 如果给定的初始化的容量是负数或者给定的负载因子不是正数
*
*/
public HashMap(int initialCapacity, float loadFactor) {
// 如果给定的初始化容量参数是负数,则会抛出参数不合法的具体异常信息
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
/*
* 如果给定的初始化容量大于默认的最大容量值(MAXIMUM_CAPACITY常量见上表格III的编号2变量),
* 则实际容量给默认的最大容量值
*/
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 如果负载因子不是正数,则会抛出参数不合法的具体异常信息
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
/*
* 计算实际容量
* 1.容量值大小要求是2的幂次方.
* 2.所以要找一个数字是2的幂次方,且大于等于给定的容量值,则实际的容量可能会跟你设定的容量值不一样
* 3.理解:则譬如你给定的容量值是14,则实际容量值是 2⁴=16, 如果你给定的容量值是16, 则实际容量值也是 2⁴=16
* */
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
/*
* 1.准备扩容的临界阀值计算:实际的容量与负载因子的乘积.
* 2.理解:譬如你设置实际容量是16,负载因子是0.75,则当HashMap的使用量大小达到16*0.75=12时,
* 则需要调用resize()方法进行扩容,扩容的详细过程见下面解释
*/
threshold = (int)(capacity * loadFactor);
// 初始化Map Entry元素数组
table = new Entry[capacity];
init();
}
iii.构造二:HashMap(int initialCapacity),源码以及分析如下:
/**
* 通过给定的容量值和默认的负载因子(0.75)来构造一个空的 HashMap
*
* @param initialCapacity 给定的初始容量值
* @throws IllegalArgumentException 如果给定的初始化的容量是负数
*/
public HashMap(int initialCapacity) {
/*
* 可以看出这个构造函数只是指定了初始的容量值,
* 其实调用的是构造一HashMap(int initialCapacity,float loadFactor)这个构造函数,
* 只是负载因子用了默认的0.75(默认的负载因子常量DEFAULT_LOAD_FACTOR)
*/
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
vi. 构造三:HashMap(),源码以及分析如下:
/**
* 通过默认的初始化容量值(16)以及默认的负载因子(0.75)来构造一个空的 HashMap
*/
public HashMap() {
// 默认的负载因子常量
this.loadFactor = DEFAULT_LOAD_FACTOR;
// 通过默认的初始容量以及默认的负载因子来计算出默认的负载阀值(16 * 0.75 = 12)
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
// 默认的初始容量值
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
v. 构造四:HashMap(Map extends K, ?extends V> m),源码以及分析如下:
/**
*
* 在给定的Map m基础上来构造一个新的HashMap,其中给定的Map m的映射数据会被拷贝到新的HashMap中。
* 新构造的HashMap负载因子使用的是默认的0.75,并且其容量值会根据给定的Map m的大小来重新计算容量,
* 保证新的HashMap容量充足,具体的计算方法参考下面注释
*
* @param m 给定的基础Map,数据会被映射到新的HashMap上
* @throws NullPointerException 如果给定的Map是null,则会抛出空指针异常
*/
public HashMap(Map extends K, ? extends V> m) {
/*
*1.可以看出对HashMap的初始化构造还是调用上面的构造一HashMap(int initialCapacity,float loadFactor)
*这个构造函数,从而对新建的HashMap进行初始化.
*2.首先是根据指定的Map m的大小来重新计算容量,可以看出新创建的的HashMap的容量大小是取
* max(m的大小/默认的负载因子(0.75)的商, 默认的初始容量16) 的值作为初始化容量
*3.负载因子使用是默认的0.75
*
*/
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
//将给定的Map m的数据拷贝到新创建的HashMap中
putAllForCreate(m);
}
二、 HashMap实现具体原理分析
I. 内部存储如图:
II. 根据源码以及上图可以分析下述结论
i. HashMap 内部是以可扩容的数组(Entry[] table)形式来存数数据,每个数组元素是Entry的数据结构为单元的链表
ii. 每个Entry 都要具备基本四个属性:hash值,key值,value值,其在数组中下标位置index,如图
iii. put