zoukankan      html  css  js  c++  java
  • 分析轮子(十)- HashMap.java 之概念梳理

    注:玩的是JDK1.7版本

    一:还是原来的风格,先上一下类的继承关系图,这样能够比较清楚的知道此类的相关特性

    二:HashMap.java 的代码比较难看,所以,我看了几天,写的话也分开来写,这样能表达的更清晰,HashMap.java 的底层数据结构,本质是单向链表数组,如下所示是单向链中节点的结构信息

    三:既然 HashMap.java 的底层数据结构是单向链表数组,那么我们便可以想象一下数组和单向链表这两种数据结构的特点,然后再回头想想 HashMap.java 的实现,然后再看源码就更容易理解了,如下所示是可能的结构样子。

    1)通常应该是如下所示的结构形式,哈希值比较均匀,部分存在冲突

    2)极端情况可能是如下所示的结构形式,存在大量冲突,单向链表数组 变成了 单向链表

    3)极端情况可能是如下所示的结构形式,没有任何冲突,单向链表数组 变成了 简单的数组

    四:看完如上 HashMap.java 的底层数据结构的可能呈现的样子之后,我们再看一下 HashMap.java 中的有关属性,个人感觉可能的结构了解后,更容易理解这些属性的本质,注意:HashMap.java 的特点是可动态扩容哈!

    1)HashMap的默认初始化容量(16),表示HashMap当前最多能够装载16个元素,注意:必须是2的幂次方

        /**
         * The default initial capacity - MUST be a power of two.
         */
        static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    2)HashMap的最大容量 2的30次方=1073741824

        /**
         * The maximum capacity, used if a higher value is implicitly specified
         * by either of the constructors with arguments.
         * MUST be a power of two <= 1<<30.
         */
        static final int MAXIMUM_CAPACITY = 1 << 30;

    3)HashMap默认的装载因子(0.75f),用于衡量HashMap满的程度,0.75=3/4,换言之当HashMap中的元素超过当前容量的3/4的时候,HashMap就会进行动态的扩容

        /**
         * The load factor used when none specified in constructor.
         */
        static final float DEFAULT_LOAD_FACTOR = 0.75f;

    4)HashMap没有真正放置元素时,是一个空数组

        /**
         * An empty table instance to share when the table is not inflated.
         */
        static final Entry<?,?>[] EMPTY_TABLE = {};

    5)HashMap没有真正放置元素时,是一个空数组,注意:HashMap的容量长度必须是2的幂次方

        /**
         * The table, resized as necessary. Length MUST Always be a power of two.
         */
        transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

    6)HashMap中 key-value mapping 映射对的个数

        /**
         * The number of key-value mappings contained in this map.
         */
        transient int size;

    7)HashMap 动态扩容的临界值,每当 size>threshold 的时候,HashMap 就会动态扩容了,threshold = capacity * load factor

        /**
         * The next size value at which to resize (capacity * load factor).
         * @serial
         */
        // If table == EMPTY_TABLE then this is the initial capacity at which the
        // table will be created when inflated.
        int threshold;

    8)装载因子,用于 HashMap 是否进行动态扩容计算的变量之一,默认值是0.75f,如无必要通常不必改变

        /**
         * The load factor for the hash table.
         *
         * @serial
         */
        final float loadFactor;

     五:实验实验,玩一把,看看什么情况

    1)结论性信息的都放在了代码注释之中,如下所示(可以自己动手调整一下参数配置,跑跑看)

    /**
     * @description:玩一把HashMap
     * @author:godtrue
     * @create:2018-09-28
     */
    public class TestMap {
    
        /**
         * 开始循环的基数
         */
        public static final int START_LOOP=1;
    
        /**
         * 结束循环的基数
         */
        public static final int END_LOOP=17;
    
        /**
        *
        *@description: 测试入口,主方法
        *@param args
        *@return: void
        *@author: godtrue
        *@createTime: 2018-09-28 9:53
        *@version: v1.0
        */
        public static void main(String[] args) {
            /**
             * 此处可以调用不同的构造方法来观察,HashMap 的容量、装载因子、扩容临界值、K-V映射对的个数等重点参数之间的关系
             * 重点强调一次
             * 1:capacity 容量——HashMap最多能装载元素个数
             * 2:loadFactor 装载因子——表示HashMap满的程度,判断HashMap是否扩容的变量之一
             * 3:threshold 扩容临界值——判断HashMap是否扩容的标准( threshold = capacity * loadFactor )
             * 4:size HashMap 已经装载的元素个数——已经转载进入HashMap的 K-V mapping 映射对的个数
             *
             * 5:HashMap 能够动态扩容,当 size > threshold 时 HashMap,便会自动库容,每次扩容的长度是原来 容量 的 2 倍
             * 6:HashMap 的 key 和 value 都可以为 null
             * 7:HashMap 是非线程安全的
             *
             */
            //Map hashMap = new HashMap<String,String>();
            Map hashMap = new HashMap<String,String>(1);
            for(int i = TestMap.START_LOOP;i<TestMap.END_LOOP;i++){
                /**
                 * 此处可以控制 key 值,来观察一下运行的情况
                 */
                //hashMap.put(null,"i am godtrue"+i);
                //hashMap.put("godtrue","i am godtrue"+i);
                hashMap.put("godtrue"+i,"i am godtrue"+i);
                printMapInfo(hashMap,i);
            }
            System.out.println("hashMap is : "+hashMap);
        }
    
        /**
        *
        *@description: 将 Map 的参数信息打印到控制台,主要是打印 容量、装载因子、扩容临界值、K-V映射对的个数 等参数信息
        *@param map
        *@param i
        *@return: void
        *@author: godtrue
        *@createTime: 2018-09-28
        *@version: v1.0
        */
        private synchronized static void printMapInfo(Map map,int i){
            System.out.println("添加第 "+i +" 个元素后");
            printMapMethodInfo(map,"capacity");
            printMapFieldInfo(map,"loadFactor");
            printMapFieldInfo(map,"threshold");
            printMapFieldInfo(map,"size");
            System.out.println("***********************************************
    ");
        }
    
        /**
        *
        *@description:  将 Map 的属性信息打印到控制台,主要是打印 装载因子、扩容临界值、K-V映射对的个数 等参数信息
        *@param map
        *@param property
        *@return: void
        *@author: godtrue
        *@createTime: 2018-09-28
        *@version: v1.0
        */
        private static void printMapFieldInfo(Map map,String property){
            try {
                Class<?> mapType = map.getClass();
                Field field = mapType.getDeclaredField(property);
                field.setAccessible(true);
                System.out.println(field +" : "+ field.get(map));
            }catch (Exception e){
                System.err.println("e is :"+e);
                e.printStackTrace();
            }
        }
    
        /**
        *
        *@description: 将 Map 的方法信息打印到控制台,主要是想打印 容量 的信息
        *@param map
        *@param property
        *@return: void
        *@author: godtrue
        *@createTime: 2018-09-28
        *@version: v1.0
        */
        private static void printMapMethodInfo(Map map,String property){
            try {
                Class<?> mapType = map.getClass();
                Method method = mapType.getDeclaredMethod(property);
                method.setAccessible(true);
                System.out.println(method +" : "+ method.invoke(map));
            }catch (Exception e){
                System.err.println("e is :"+e);
                e.printStackTrace();
            }
        }
    }

     2)仔细观察如下日志,可以印证上述代码注释中的部分结论,注意:请重点关注 capacity、loadFactor、threahold、size之间的变化关系

    添加第 1 个元素后
    int java.util.HashMap.capacity() : 1
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 0
    transient int java.util.HashMap.size : 1
    ***********************************************
    
    添加第 2 个元素后
    int java.util.HashMap.capacity() : 2 //扩容
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 1
    transient int java.util.HashMap.size : 2
    ***********************************************
    
    添加第 3 个元素后
    int java.util.HashMap.capacity() : 4 //扩容
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 3
    transient int java.util.HashMap.size : 3
    ***********************************************
    
    添加第 4 个元素后
    int java.util.HashMap.capacity() : 4
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 3
    transient int java.util.HashMap.size : 4
    ***********************************************
    
    添加第 5 个元素后
    int java.util.HashMap.capacity() : 8 //扩容
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 6
    transient int java.util.HashMap.size : 5
    ***********************************************
    
    添加第 6 个元素后
    int java.util.HashMap.capacity() : 8
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 6
    transient int java.util.HashMap.size : 6
    ***********************************************
    
    添加第 7 个元素后
    int java.util.HashMap.capacity() : 8
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 6
    transient int java.util.HashMap.size : 7
    ***********************************************
    
    添加第 8 个元素后
    int java.util.HashMap.capacity() : 8
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 6
    transient int java.util.HashMap.size : 8
    ***********************************************
    
    添加第 9 个元素后
    int java.util.HashMap.capacity() : 16 //扩容
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 12
    transient int java.util.HashMap.size : 9
    ***********************************************
    
    添加第 10 个元素后
    int java.util.HashMap.capacity() : 16
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 12
    transient int java.util.HashMap.size : 10
    ***********************************************
    
    添加第 11 个元素后
    int java.util.HashMap.capacity() : 16
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 12
    transient int java.util.HashMap.size : 11
    ***********************************************
    
    添加第 12 个元素后
    int java.util.HashMap.capacity() : 16
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 12
    transient int java.util.HashMap.size : 12
    ***********************************************
    
    添加第 13 个元素后
    int java.util.HashMap.capacity() : 32 //扩容
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 24
    transient int java.util.HashMap.size : 13
    ***********************************************
    
    添加第 14 个元素后
    int java.util.HashMap.capacity() : 32
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 24
    transient int java.util.HashMap.size : 14
    ***********************************************
    
    添加第 15 个元素后
    int java.util.HashMap.capacity() : 32
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 24
    transient int java.util.HashMap.size : 15
    ***********************************************
    
    添加第 16 个元素后
    int java.util.HashMap.capacity() : 32
    final float java.util.HashMap.loadFactor : 0.75
    int java.util.HashMap.threshold : 24
    transient int java.util.HashMap.size : 16
    ***********************************************
    
    hashMap is : {godtrue4=i am godtrue4, godtrue5=i am godtrue5, godtrue2=i am godtrue2, godtrue3=i am godtrue3, godtrue8=i am godtrue8, godtrue9=i am godtrue9, godtrue6=i am godtrue6, godtrue7=i am godtrue7, godtrue1=i am godtrue1, godtrue10=i am godtrue10, godtrue12=i am godtrue12, godtrue11=i am godtrue11, godtrue14=i am godtrue14, godtrue13=i am godtrue13, godtrue16=i am godtrue16, godtrue15=i am godtrue15}
    
    Process finished with exit code 0

     六:几个为什么?

    1)为什么 HashMap 的默认容量是 16, 并且强调容量必须是 2 的幂次方呢?

          HashMap 的容量必须是 2 的幂次方,主要是出于性能的考虑,可以使用 位于运算 来计算单向链表数组的下标位置

          详情可参考 

         http://www.hollischuang.com/archives/2091

         http://www.cnblogs.com/chenssy/p/3521565.html

         https://blog.csdn.net/justloveyou_/article/details/62893086

         默认值为什么是16呢?

         首先,16 是 2的4次方,符合容量是 2 的幂次方的强性规定,其次,我猜测 16 可能是一个样本比较集中的 HashMap的容量

    2)为什么 HashMap 的默认装载因子是 0.75f,并且不建议自定义呢?

          HashMap 的默认装载因子是0.75f,主要是时间和空间成本上一种折衷。

          详情可参考

          http://alex09.iteye.com/blog/539545/

    3)为什么 HashMap 在扩容的时候,总是扩大原来容量的 2 倍呢?

          首先,容量扩大 2 倍后,仍然符合容量是 2 的幂次方的强性规定(注意:容量是2的幂次方),其次,同样是出于性能考虑,直接通过左移移位便可实现

    七)本篇,编写的过程中参看了 http://www.hollischuang.com/archives/2416

  • 相关阅读:
    JavaScript 正则表达式
    git常用命令
    用纯css使内容永远居在页面底部
    Oracle中随机抽取N条记录
    表数据回复到某个时候
    oracle同名存储过程被覆盖后如何恢复(转)
    mybatis+spring+mysql
    定位
    关于js的闭包和复制对象
    idea展示runDashboard的窗口
  • 原文地址:https://www.cnblogs.com/godtrue/p/9714415.html
Copyright © 2011-2022 走看看