HashMap原理及部分底层源码分析

 目录

数据结构中的哈希散列

hashCode

HashMap的数据结构   

HashMap的使用过程

详细部分底层代码分析:(基于jdk1.7,对比1.6)

1、初始化

2、Entry

3、put方法

4、自动扩容

5、从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

   HashMap作为每一个java开发人员必会的入门工具类,也成为广大校招面试的入门级必考题目。本文对HashMap由浅入深进行剖析,从数据结构讲起,然后分析原理,最后基于jdk1.7对源码进行简单分析,并在文章的末尾给出jdk1.8的优化思想。

【这里有一个思想就是,不要以为每一个版本的jdk实现方式都是一样的,jdk也是人写的,不断的版本更新就是对底层代码进行不断的优化~~~】

 

数据结构中的哈希散列

    在《数据结构》这门课中的查找章节学过,使用哈希散列,能使对一个数据序列的访问过程更加迅速有效,通过某种算法的散列函数,数据元素将被更快地定位。而通过某种算法定位的不同元素,可能会对应列表中的同一位置,这时就需要处理冲突,处理冲突的方法有开放地址法、链地址法等…

    假如,这里使用的哈希函数是(value / 3的余数),需要散列的数列有6 7 8 9 10,那么经过哈希函数计算后的值分别是 6%3=0, 7%3=1, 8%3=2, 9%3=0, 10%3=1,所以此时得到的0 1 2 0 1值中可以看到0和1两个数出现冲突,需要解决冲突,因为一个坑放不下两个数。实际情况中,一个坑会冲突N个数。

hashCode

    Object类中有个方法HashCode,可以通过这个方法获取所有实例对象的哈希值。在new的时候,会在内存中给对象分配一个地址,所以每个对象在内存中都会对应一个物理地址,然后jdk会根据这个物理地址,通过一个哈希函数计算得到int类型的hashCode。

 

HashMap的数据结构   

    底层基于数组+链表每一个数组对应一个hashMap的Entry,而每一个Entry包含key和value,通过map.entrySet得到entry,然后通过entry.getKey和entry.getValue得到key和value.

 

HashMap的使用过程

    大致概括:

    1、当你new hashMap的时候,会初始化一个默认容量的数组,

    2、然后put元素时,会自动调用这个对象的key的hashCode值,来决定要放到哪个数组位置中,

    3、当hashCode相同时,有可能是操作者put了两次同一个元素,这时候就要通过equals方法来判断key值是否相同,若相同,则抛弃key,覆盖value。【hashCode相同只能代表我们是同一个位置,但是值可能不同,所以equals不一定相等。但是若equals相同,则hashCode一定相同】。若equals方法不同,就会放到同一个数组位置中,但是放不下,就要解决冲突,hashMap底层使用链地址法来解决冲突,即使用指针来指向相同hashCode元素,如下图,横向一行都是相同的hashCode。

    4、当put到一定程度后装不下了,hashMap会进行自动扩容,这个扩容要在另一块内存中新建一个更大容量的数组,然后把原有数组数据拷贝进去并重新散列,所以当元素很多时,这个过程非常耗时,最好在new的时候,预计好需要的空间,一步到位。

    《HashMap原理及部分底层源码分析》

详细部分底层代码分析:(基于jdk1.7,对比1.6)

 

1、初始化

 

《HashMap原理及部分底层源码分析》

    常用的三个构造函数,第一个可以指定初始容量和负载因子。第二个默认负载因子为0.75,可以指定初始容量。第三个默认初始容量为16,负载因子为0.75.【负载因子是什么,假如0.75,意思就是,当元素到达我数组容量的75%,即进行扩容】

    代码中loadFactor就是负载因子,负载因子越大(越接近1),则数组会填的越满的时候才会进行扩容,但此时,后面由于解决冲突生成的链表就会越长,查找效率就会越低下。但负载因子如果过小,数组刚刚有一点数据,后面的拉链也很少,就马上就要开始扩容,扩容又要复制数组重新散列,增加时间成本,浪费内存。因此,要平衡时间和空间,合理设置负载因子。一般情况下使用默认的就ok。

《HashMap原理及部分底层源码分析》

—————————————————————-

《HashMap原理及部分底层源码分析》

2、Entry

《HashMap原理及部分底层源码分析》

    HashMap中的Entry实现了Map接口中的Entry内部接口,所以Entry是HashMap的内部类,四个属性分别为:键、值、指向下一个链表结点的指针、散列(哈希)值。

    所以Entry只是一个类,而key和value都是它里面的属性,只要得到这个Entry,就可以得到属性key和value。

3、put方法

《HashMap原理及部分底层源码分析》

    put方法源码可以看出,当要放入一对键值对(一个Entry)时,会根据key的hashCode决定放入数组的位置,若该hashCode已存在,有可能是操作者put了两次同一个key,这时候就要通过equals方法来判断key值是否相同,若相同,则抛弃key,并将新的Entry的value覆盖原有Entry的value(hashMap不允许key相同,但是允许value相同);若不相同,说明hash冲突,将新的Entry与原来已经有的Entry合并成新的链表,并且由于速度关系,将新的Entry插入在链头。

《HashMap原理及部分底层源码分析》

4、自动扩容

《HashMap原理及部分底层源码分析》

    当数组长度不够,达到容量*负载因子时,则需要自动扩容,数组容量扩充为原来的两倍,在下图的resize方法中新建Entry数组,然后用transfer方法将原有Entry数组的元素拷贝到新的Entry数组中【原数组中的数据必须重新计算其在新数组中的位置,并放进去(重新散列)】。

《HashMap原理及部分底层源码分析》

5、从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

 

————————————————我是华丽的分割线—————————————————-

 

    jdk1.8中,对HashMap做了较大优化,最大的优化是优化jdk1.7中拉链解决冲突,当冲突元素长度小于8时,使用链表存储,当链表长度大于8时,转用红黑树对拉链进行存储,提高访问速度。数据结构为:数组+链表+红黑树。

    《HashMap原理及部分底层源码分析》

 

    原文作者:少年做自己的英雄
    原文地址: https://blog.csdn.net/qq_26012495/article/details/80109887
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞