HashMap源码分析

       最近一直特别忙,好不容易闲下来了。准备把HashMap的知识总结一下,很久以前看过HashMap源码。一直想把集合类的知识都总结一下,加深自己的基础。我觉的java的集合类特别重要,能够深刻理解和应用这些集合类能够让自己写的程序上一步台阶。

        本文主要根据自己学习与使用HashMap来解析HashMap的源码,深入到HashMap的内部结构和实现,增强自己的基础知识。同时会借鉴网上的相关资料,深入理解HashMap.

HashMap的内部存储结构

        一提到HashMap我们就知道键值对,即一个键对应一个值。可能我们经常会使用HashMap,但是并不关注里面的内部实现。今天我们就来学习一下HashMap的内部实现。

         HashMap是一种以键值对存储数据的容器,每个对象在java中都会有一个hashCode,HashMap正是借每个对象的HashCode来组织键值对的存储,因为HashCode的特性,使得HashMap以非常快速和高效地地根据键值key进行数据的存取。键值对的具体实现在HashMap内部会封装成一个Entry[] table,Entry[] table是键值对的表现形式。下图为HashMap的存储结构。HashMap中Value可以相同,但是键不可以相同.

       上图可以看出来HashMap是数组+链表的存储结构,数组的每一个元素是一个链表的表头。这样的结构能够综合2个经常使用的数据结构的特点,数组查找、遍历快,链表增加、删除快。

HashMap的属性

       static final int DEFAULT_INITIAL_CAPACITY = 16;//默认初始化加载容量,即table数组的长度。

       static final int MAXIMUM_CAPACITY = 1 << 30;//最大的容量。1左移30位,2的30次方:1073741824

       static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子为0.75

       transient Entry[] table;//table数组,上图的黄色部分。Entry为蓝色部分

       transient int size;//HashMap存储元素的数量。

       int threshold;//阀值  table的长度*加载因子(默认wei0.75)

       final float loadFactor;//加载因子

       transient volatile int modCount;//修改次数

       注:如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就  是,用transient关键字标记的成员变量不参与序列化过程。

              volatile为同步变量,保证每次都读取的值是最新的。

HashMap的构造方法

        public HashMap() {//最常用的改造方法

              this.loadFactor = DEFAULT_LOAD_FACTOR;//默认加载因子,0.75

              threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);//阀值    默认的容量*加载英子。12

              table = new Entry[DEFAULT_INITIAL_CAPACITY];//table数组的默认为16,容量为16,切记容量不等于HashMap存储的元素数量,容易混淆。

              init();//初始化方法,里面是个空

          }

        默认的构造方法开辟16个大小的空间。还有另外一个构造方法我们使用的比较少,当你觉的默认的HashMap的存储空间浪费时(容量达到3/4时HashMap就会调用resize扩大为原来的2倍),可以使用下面一个。

       public HashMap(int initialCapacity, float loadFactor) {//初始化容量,加载因子

               if (initialCapacity < 0)//容量不能小于0

                      throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);

              if (initialCapacity > MAXIMUM_CAPACITY)//大于允许最大的容量时,设置未最大值

                      initialCapacity = MAXIMUM_CAPACITY;

             if (loadFactor <= 0 || Float.isNaN(loadFactor))//加载因子小于大于0或者不是数字的时候,报非法加载因子异常

             throw new IllegalArgumentException("Illegal load factor: " +

             loadFactor);

             // Find a power of 2 >= initialCapacity

            int capacity = 1;

            while (capacity < initialCapacity)//实际的开辟的空间要大于传入的第一个参数的值。

            capacity <<= 1;//这是一个重点,capacity才是容量。

            this.loadFactor = loadFactor;//加载因子设置为传入的值

            threshold = (int)(capacity * loadFactor);//阈值

           table = new Entry[capacity];//数组

           init();

        }

         此外还有2个构造方法基本上很少用到,感兴趣的可以自己看看。HashMap最重要的方法是put和get方法,下面我们重点看看这2个方法。

HashMap的put方法分析

         public V put(K key, V value) {//很熟悉的方法

              if (key == null)//如果key==null,直接设置空的

                     return putForNullKey(value);

              int hash = hash(key.hashCode());//获得hash值

              int i = indexFor(hash, table.length);//根据hash值找到此键值对应该放在数组的第几个位置。

              for (Entry e = table[i]; e != null; e = e.next) {//拿到放置该键值对的链表,

                     Object k;

              if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//如果原来的存在,直接替换值

                      V oldValue = e.value;

                      e.value = value;

                      e.recordAccess(this);

                      return oldValue;

                  }

           }

           modCount++;

           addEntry(hash, key, value, i);//不存在就新增

          return null;

     }

          put方法会先判断键是不是空,如果为空就调putForNullKey方法。方法如下:

          private V putForNullKey(V value) {

                for (Entry e = table[0]; e != null; e = e.next) {//获得第一个链表,遍历查找是否原来就存储为null的键值对

                 if (e.key == null) {//如果存在

                       V oldValue = e.value;//记录下老的值

                       e.value = value;//把新值设置进去

                       e.recordAccess(this);

                       return oldValue;//返回老值

                   }

               }

                 modCount++;

                addEntry(0, null, value, 0);//Key 为null,则将Entry放置到第一桶table[0]中

                return null;

         }

          计算Hash值的方法如下:

           static int hash(int h) {//根据特定的hashcode 重新计算hash值

                      h ^= (h >>> 20) ^ (h >>> 12);

                       return h ^ (h >>> 7) ^ (h >>> 4);

              }

            static int indexFor(int h, int length) {//匹配到具体的桶当中,

                       return h & (length-1);//相当于int i = hash %Entry[].length;得到i后,就是在Entry数组中的位置

             }

       整个put方法的流程如下:

        首先判断键是否为空,如果为空在判断是否已经存在键为空的键值对,存在就更新值,不存在就新增;

        如果键不为空,则获取这个Key的hashcode值,根据此值确定键值对的存储位置;遍历所在桶中的Entry链表,查找其中是否已经有了以Key值为Key存储的Entry对象,若已存在,定位到对应的Entry,其中的Value值更新为新的Value值;返回旧值;若不存在,则根据键值对 创建一个新的Entry对象,然后添加到这个桶的Entry链表的头部。当前的HashMap的大小(即Entry节点的数目)是否超过了阀值,若超过了阀值(threshold),则增大HashMap的容量(即Entry[] table 的大小),并且重新组织内部各个Entry排列。

HashMap的get方法分析

         public V get(Object key) {

              if (key == null)//如果键等于null,调用getForNullKey

                     return getForNullKey();

             int hash = hash(key.hashCode());//获得hash值,

             for (Entry e = table[indexFor(hash, table.length)];

                    e != null;

             e = e.next) {//indexfor计算存储位置,根据位置遍历链表

                    Object k;

            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

                    return e.value;//拿到对应的值

             }

                   return null;

       }

         如果键等于null,调用getForNullKey

         private V getForNullKey() {

                 for (Entry e = table[0]; e != null; e = e.next) {//键为null的键值对存储在数组的第一个元素,

                 if (e.key == null)//如果存在则返回值

                         return e.value;

            }

                        return null;//不存在返回null

}

       get方法比较简单,比较容易理解。主要流程如下:

        首先判断键是否为空,如果为空在table[0]中取键为空的键值对,如果不存在为空的则返回null。

        如果键不为空,则获取这个Key的hashcode值,根据此值确定该键值对的存储位置;遍历所在桶中的Entry链表,查找其中是否已经有了以Key值为Key存储的Entry对象,若已存在,定位到对应的Entry,获得Value值;返回旧值;若不存在,则返回空。

HashMap的总结

        本文主要讲了HashMap的存储结构以及基本属性、构造方法,同时分析了比较常用的put和get方法。其他方法请读者自行查看。在看HashMap的源码时,我认为以下几个方面比较重要,能够理解到以下的几点,搞定HashMap基本不成问题。

         HashMap的存储结构,以及为什么要这样进行存储。

         HashMap的各个属性的含义,以及其扩容的策略(扩容算法)。

         Hash值的计算.确定桶的位置。

         HashMap中的value可以相同,键不可以相同。

         HashMap是非线程安全的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容

  • HashMap 是 Java 面试必考的知识点,面试官从这个小知识点就可以了解我们对 Java 基础的掌握程度。网...
    野狗子嗷嗷嗷阅读 6,636评论 9 107
  • 一、基本数据类型 注释 单行注释:// 区域注释:/* */ 文档注释:/** */ 数值 对于byte类型而言...
    龙猫小爷阅读 4,243评论 0 16
  • HashMap<K,V> Entry<K,V> table = new Entry<K,V>(expectedNu...
    _挑灯看剑_阅读 223评论 0 0
  • 5.1、对于HashMap需要掌握以下几点 Map的创建:HashMap() 往Map中添加键值对:即put(Ob...
    rochuan阅读 653评论 0 0
  • 荒芜中,出现一幢略显破旧的小楼,复古,精致,在正中上方,圆圆的一个“旅”字。 别无去处,那就进去吧。 小旅馆的样子...
    凌空于梦阅读 418评论 0 2