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

一、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(Mapextends 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 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. 内部存储如图:

Java java.util.HashMap实现原理源码分析_第1张图片Java java.util.HashMap实现原理源码分析_第2张图片

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

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

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

Java java.util.HashMap实现原理源码分析_第3张图片

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

 Java java.util.HashMap实现原理源码分析_第4张图片



 



你可能感兴趣的:(Java)