干货 | 从源码看HashMap键值对集合

Posted e安在线

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了干货 | 从源码看HashMap键值对集合相关的知识,希望对你有一定的参考价值。

与其相忘于江湖,不如点击“蓝字”关注

之前我们看过了两种类型的集合,ArrayList集合和LinkedList集合,两种集合各有优势,我们不具体说了,但是本篇要看的集合可以完成它们完成不了的任务。比如:现有一篇文章,要你统计其中出现了哪些单词,每个单词总共出现了几次。这个问题很明显需要记录两个变量(某单词及其出现次数),但是我们之前介绍的集合都只能同时存储一种类型的变量,无法实现对应的效果。


一、键值对集合(官方名称映射)

我们的HashMap又可以叫做键值对集合(官方名称映射),比如:

想要研究一个具体的类,先要看看它的父类或者父接口。HashMap实现了Map<K,V>接口,K表示键,V表示值,一个键对应一个值,整个集合中键是唯一的不可重复。

干货 | 从源码看HashMap键值对集合

接口中定义了 V get(Object key); 方法用来根据指定的键值,返回该键所对应的value值,有方法 V put(K key, V value); 添加指定键值和value的一条记录到集合中,有方法 V remove(Object key); 根据指定的value值删除一条或者多条记录等等,当然这些方法都只是声明,我们马上可以看看HashMap对这些方法的具体的实现是什么。


二、HashMap的实现原理

 HashMap是Map的一种实现,它具体实现了上述Map接口中的所有方法。我们看看它的内部是以什么样的形式组织和存储内容的。

干货 | 从源码看HashMap键值对集合
干货 | 从源码看HashMap键值对集合

上述代码主要从代码的角度展现了HashMap的内部数据是以什么样的形式来存储的。我们可以看到,HashMap中定义了初始容量为16,最大容量,默认的 DEFAULT_LOAD_FACTOR(具体是什么下面再说) 参数值,实际上HashMap内部存储为数组加链表,定义了一个Node类数组,每个Node结点指向一个链表的头部串联整个链表。(单向链表)

干货 | 从源码看HashMap键值对集合

Node结点其实构成的是一个单向链表,每个结点由 final K key;,不可更改的键,可以修改的 V value; 值,还有指向下一个结点的指针Node<K,V> next; 组成,当然还包含了对结点的操作方法。


三、添加元素到集合中

 我们使用put方法添加元素到集合中:

干货 | 从源码看HashMap键值对集合

接下来我们来解释一下整体的调用过程,首先在调用put方法时候,调用了另外的一个方法 putVal(看起来十分恶心),传过去一个key个hash值,还有key和value,还有两个布尔参数(后面我们会知道他们的作用)。


首先if判断这个HashMap是否是null,如果是的话,调用resize方法初始化一个HashMap(容量为16,loadFactor参数为0.75等其他参数值),然后 (p = tab[i = (n - 1) & hash]) == null 用我们想要插入的元素的hash值和HashMap容量取模判断将要存储的位置,因为我们知道每个对象的hash值是不同的,所以在插入元素的时候用他们的hash值和整个HashMap数组长度取模使得他们根据自己的hash存储在HashMap的固定位置上,如果我们将要插入节点的位置上是空的就 tab[i] = newNode(hash, key, value, null);,新建节点直接存放在此位置上。

干货 | 从源码看HashMap键值对集合

 假如我们使用hash取模之后得到的索引位置为3,发现此处为空,于是占用他。

干货 | 从源码看HashMap键值对集合

我们接着看,刚才是预定的位置没被使用,那如果已经被占用了怎么办?

干货 | 从源码看HashMap键值对集合

这行代码紧接着,对占用我们位置的结点键的值和我们的键的值进行比较,如果一样说明,这是对这条记录的修改,于是准备覆盖原位置的结点将p赋值给e。紧接着的一行代码可以先跳过设计到树,最后一个else中主要是将我们将要插入的结点链接到某个链表上。方法的尾部判断e是否为空,如果不为空说明在某条链表中找到与我们将要插入的结点的键的值一样的结点(就是需要覆盖原链表中某个结点的值),于是覆盖某个结点的值返回该结点的值。当然方法的最后对容量进行了判断,如果超过需要扩充的界限就会调用方法扩充HashMap数组的长度。


最后总结一下整个put方法的几个重要的过程:

  • 判断HashMap是否为空,是就初始化一个HashMap

  • 判断预定位置是否被占用,未被占用则占用

  • 预定位置被占用则判断是否有键值相等的结点,有则覆盖

  • 遍历之后没有找到键值相等的结点,就将此结点链接在某链表的末尾

  • 判断容量是否越界,是则扩充容量


四、删除操作

remove方法的源码如下:

干货 | 从源码看HashMap键值对集合

我们具体看看代码,第一个if语句主要是判断:如果当前的HashMap不为null并且将要删除的结点的key和capcity取模后存在于HashMap数组中,就执行余下的代码。(主要还是为了保证将要删除的元素是存在的),接下来的一句if判断主要是,如果第一个位置的结点就是我们将要删除的结点的话,就将此结点赋值node,接下来就是遍历整个链表查找将要删除的键值,如果找到赋值给node。最后根据不同情况进行删除操作,如果p=node 说明将要删除的结点是第一个结点直接将此位置赋null即可,否则,将p的next赋null即可(经过遍历p始终指向将要删除的前一个结点),最后完成删除。


最后小结一下整个删除操作的过程:


  • 传入指定的键值,将键值及其hash值传入方法removeNode

  • 通过判断HashMap是否为null以及hash取模之后的位置是否为null,提高函数执行效率

  • 在链表中寻找键值,并且做好标记准备删除

  • 删除元素结点


至于get查找返回指定的值,是否包含指定键,是否包含指定值等方法类似。


最后,因为HashMap存取没有顺序,如果对于一些需要按键存取数值的数据并且对存取顺序没有要求的话,可以考虑使用HashMap提升效率。


本篇文章主要是作者查看jdk源码总结而来,如果有不当之处,望大家指出来。

本文著作权归吴贤强所有

转载请联系“e安在线” 

END

更多资讯请点击“阅读原文”

以上是关于干货 | 从源码看HashMap键值对集合的主要内容,如果未能解决你的问题,请参考以下文章

HashMap集合底层的数据结构以及HashMap集合的存储键值对数据的过程

Java的HashMap键值对存储结构解析

源码解读JDK1.8 中 ConcurrentHashMap 不支持空键值对源码剖析

java问题,我想在java中存储键值对,以便使用,但是键值对的键和值都有重复元素,使用hashmap会产生覆盖。

HashMap的基本使用

java:我想使用键值对,并且键或者值可以重复,在java中找得到对应的类吗?