您的位置:首页 > 编程语言 > Java开发

Java java.util.HashMap实现原理源码分析

2014-08-06 14:46 1091 查看
一、HashMap源码概览

I. 可以结合JDK源码包了解java.util.HashMap实现原理

II. 继承抽象类java.util.AbstractMap,实现了三个接口,分别是:java.util.Map、java.lang.Cloneble、 java.io.Serializable

III. 主要的成员变量

主要成员变量

编号变量代码变量注释
1static final int DEFAULT_INITIAL_CAPACITY = 16;

默认的初始化容量大小为16,可以通过HashMap(int initialCapacity) 或者HashMap(int initialCapacity,float loadFactor)这两个构造函数来改变此容量大小,但是实际的容量值可能与你设定的值不同(具体操作见下面解释
2static final int MAXIMUM_CAPACITY = 1 << 30;最大的容量值,1左移30位,即2³⁰
3static final float DEFAULT_LOAD_FACTOR = 0.75f;默认的加载因子(作用见下面解释)
4transient Entry[] table;Entry数组, HashMap内部存储实际就是Entry对象数组(Entry详细解释见下面解释,关键字transient的作用见我另一博客说明, 点击打开链接 ). 此数组如果需要的话,大小可以被重置,但是其长度要一直是2的幂次方
5transient int size;此Map的大小(即k-v映射的长度)
6int threshold;Map准备扩容的临界阀值(临界阀值的计算方法见下面的解释),即在Map在使用过程中,如果发现Map使用量(即keys大小)已经超过此阀值,则会调用resize()方法进行重置大小(会进行再哈希,将旧数据交换到扩容后的Map容器中,详细步骤见下面解释)
7final float loadFactor;加载因子,默认值为此表格中编号3的变量对应的值,即0.75,可以通过构造HashMap(int initialCapacity, float loadFactor) 来改变此值
8transient volatile int modCount;
VI.构造方法
i. 默认总共有四个构造函数,归根到底可以总结只有一个构造,下面将逐一介绍
ii. 构造一:HashMap(int initialCapacity,float loadFactor),源码以及分析如下:

/**
* 通过给定的容量值和加载因子来构造一个空的 <tt>HashMap</tt>
* 但是指定的这两个参数值的范围是有要求的,具体看下面代码段的分析
*
* @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)来构造一个空的 <tt>HashMap</tt>
*
* @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)来构造一个空的 <tt>HashMap</tt>
*/
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基础上来构造一个新的<tt>HashMap</tt>,其中给定的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);
}


vi. 总结:可以看出上述的构造方法,其实归根到底可以总结为,在创建HashMap时,需要两个基本的要素:给定合理的容量、给定合理的负载因子.

二、 HashMap实现具体原理分析

I. 内部存储如图:





II. 根据源码以及上图可以分析下述结论

i. HashMap 内部是以可扩容的数组(Entry[] table)形式来存数数据,每个数组元素是Entry的数据结构为单元的链表

ii. 每个Entry 都要具备基本四个属性:hash值,key值,value值,其在数组中下标位置index,如图



iii. put<K, V>的流程,即添加<K, V>时,HashMap内部的工作流程图如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: