HashMap的put和get的底层实现原理
在了解HashMap的底层实现原理的时候,我们首先了解HashMap的底层结构。
HashMap的底层是基于数组+链表实现的。但是jdk1.7和jdk1.8的实现有点不同。
HashMap的存储结构:
HashMap的底层是基于数组+链表实现的。但是jdk1.7和jdk1.8的实现有点不同。
HashMap的存储结构:
1.hashmap底层是以数组方式进行存储。将key-value对作为数组中的一个元素进行存储。
2.key-value都是Map.Entry中的属性。其中将key的值进行hash之后进行存储,即每一个key都是计算hash值,然后再存储。每一个Hash值对应一个数组下标,数组下标是根据hash值和数组长度计算得来。
3.由于不能保证两个不同的key有相同的hash值,即该位置的数组中的元素出现两个,对于这种情况,hashmap采用链表形式进行存储。
Jdk1.7:
基本构造:
1.初始化桶大小,因为底层是数组,所以这是数组默认的大小。
2.桶最大值。
3.默认的负载因子(0.75)
4.table 真正存放数据的数组。
5.Map 存放数量的大小。
6.桶大小,可在初始化时显式指定。
7.负载因子,可在初始化时显式指定。
HashMap的默认容量为16,负载因子是0.75,当我们在使用HashMap的时候,不断给HashMap中存放值,当数量达到了12(16*0.75)的时候,就需要进行扩容,扩容涉及到了rehash复制数据等操作,所以扩容会消耗资源。因此我们在使用的时候,可以提前预估HashMap的容量最好。
Put的底层实现原理:
首先我们去判断当前数组是否需要初始化,如果不需要初始化,就判断key值是否为空,如果为空,那就直接put一个空值进去,然后根据key去计算hashcode值,这样的话我们就可以知道索引的下标(元素存放位置),如果那个位置是 一个链表则需要遍历判断里面的 hashcode、key 是否和传入 key 相等,如果相等则进行覆盖,并返回原来的值如果当前位置没有元素,那就直接添加一个Entry对象到当前位置(调用addEntity写入Entity时需要判断是否需要扩容)。
Get的底层实现原理:
首先也是根据 key 计算出 hashcode,然后定位到具体的桶中。
判断该位置是否为链表。
不是链表就根据 key 的 hashcode 是否相等来返回值。
为链表则需要遍历直到 key 及 hashcode 相等时候就返回值。
啥都没取到就直接返回 null
Jdk1.7的缺点:当hash冲突严重时,形成的链表会越来越长,这样的话,查询的效率会越来越低。
因此在jdk1.8中进行了优化。
Jdk1.8:
Put的底层实现原理:
1.判断当前桶是否为空,空的就需要初始化(resize 中会判断是否进行初始化)。
2、根据当前 key 的 hashcode 定位到具体的桶中并判断是否为空,为空表明没有 Hash 冲突就直接在当前位置创建一个新桶即可。
3、如果当前桶有值( Hash 冲突),那么就要比较当前桶中的 key、key 的 hashcode 与写入的 key 是否相等,相等就赋值给 e,在第 8 步的时候会统一进行赋值及返回。
4、如果当前桶为红黑树,那就要按照红黑树的方式写入数据。
5、如果是个链表,就需要将当前的 key、value 封装成一个新节点写入到当前桶的后面(形成链表)。6、接着判断当前链表的大小是否大于预设的阈值,大于时就要转换为红黑树。
7、如果在遍历过程中找到 key 相同时直接退出遍历。
8、如果 e != null 就相当于存在相同的 key,那就需要将值覆盖。
9、最后判断是否需要进行扩容。
Get方法的底层实现原理:
1、首先将 key hash 之后取得所定位的桶。
2、如果桶为空则直接返回 null 。
3、否则判断桶的第一个位置(有可能是链表、红黑树)的 key 是否为查询的 key,是就直接返回 value。
4、如果第一个不匹配,则判断它的下一个是红黑树还是链表。
5、红黑树就按照树的查找方式返回值。
6、不然就按照链表的方式遍历匹配返回值。
HashMap的遍历方式:entrySet(推荐使用,因此使用entrySet遍历后,可以取出key和value)、keyset(需要通过key来取出value)等等方法
当我们给hashmap中存储值(put)的时候,hashMap的底层首先去通过key进行hash计算,通过计算出的hashcode值和数组的长度进行取模,从而算出该值应该存放的地址/下标(index)
由于在计算中位运算比取模运算效率高的多,所以HashMap规定数组的长度为 2^n。这用
2^n - 1做位运算与取模效果一致,并且效率还要高出许多。