HashMap
其实这篇不想写这么早也不想写这个题目,但是明天要去考科目一并且星期天要补5.1假期的课,所以不得不写这一篇,本来是准备写volatile关键字的,不过这一篇也也不差,肯定不会水过去的。
有些时候我们可能需要一种数据结构来存放数据,下面就是一个个很简单的HashMap的使用方法。
难道我只讲这么简单的东西吗?那是肯定不会的,我将从hashmap的put方法阐述数据的存放过程,接下来先进行一些基础知识的铺垫。
- Hash
什么是hash,下面是我从百度上摘抄的定义
Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
总的来说,hash就是一个计算特征值的函数。这个函数要足够的散列,尽量做到每个数据的hash值都不同。
- HashMap
HashMap在jdk1.7之前是用数组+链表实现的一种存放数据的数据结构。在jdk1.8之后在数据达到一定的大小会转成数组+链表+红黑树的形式,我不会讲解红黑树扩容机制以及转成红黑树的过程(自己也没弄太懂),我把我已经了解的部分记录下来。虽然没有扩容机制和树化的讲解,但是我讲的这一部分也足够大家消化理解了。 - HashMap存放数据的形式
HashMap是通过键值对的方式来存放数据的,也就是key=value,例如上面的实例代码,其中的储存结构就是“张三 = 18”。链表的节点的数据包括hash,key,value,以及next,next是当前链表节点的后继。如果计算出两个key的hash(也就是hash碰撞)一模一样,那么就会使用链表将数据存起来。也就是下面的样子
基础知识差不多就这些了,知道这些就能够理解我接下来要阐述的代码了。
-------------------------------------------------------------------------------------------------------------我是一个分割线-----------------------------------
put()方法
HashMap是通过put方法来存放数据的,先进入到put方法里面看看
put方法调用了putval这个方法,继续进入到putval方法里面
稍微有一点长,但是我不会全部讲完,这个put方法里面涉及到了树化的代码,我将会略过。
putval()方法有5个形参,最重要的前三个,也就是hash,key,value,hash是计算出来的hash值,key是要存放数据的钥匙,value就是要存放的数据。传进来的hash在上一个put方法里面就已经计算完成了,先进入到hash方法里面看看,是怎么计算出hash值。
就这么简单,先判断key是否等于空,如果不等于空就用hashCode和经过右移16位hashCode进行异或运算。
(图片来源于网络)
计算过程就是上图所示,通过这个函数可以得到一个hash值,然后将这个hash传到putval方法里面。
putVal方法首先进行进行判断
如果数组满了或者数组为空,那么就进行扩容,扩容方法也就是resize,这个方法我就不具体讲了,大家知道有这么回事就行。
接着继续进行判断
这里又进行一次运算,计算出存放到数组的具体下标,(n - 1) & hash。
hash是上一个方法计算好然后传进来的hash,n是整个数组的长度,HashMap规定了数组的长度必须是2的n次方,至于为什么这么做,我或许能够阐述清楚其中一个原因,2的n次方 - 1之后得到的值转成2进制全为1,这样的话可以避免长度影响到hash的值,使hash值只与本身有关。假设数组的长度是16,那么计算的过程就应该如下图所示
(图片来源于网络)
至此已经完成了所有的计算存放到数组的下标过程,总图来总结一下
(图片来源于网络)
如果该下标的位置没有数据,那么直接把数据存进去。如果这一步判断为假,那么就转到else代码块。
一步一步来剖析。
这也是一个判断,意思是如果新节点和旧节点相等的话那么就用一个另外一个新节点来装载旧的节点,这一步是为下一步做准备的。
这里的代码就是树化过程了,具体的我就不多说了,有点复杂,略过。直接进入到下一步。
如果旧节点不等于空,那么用一个变量来存放旧节点的值,接下来的一个判断用来是否覆盖原来旧节点的值,onlyIfAbsent是一个boolean类型的变量,官方的注释是这么描述的
如果该值为true,那么则不覆盖旧值。put方法已经为我们传进来flase,所以这边会覆盖掉原来的旧值。afterNodeAccess方法没有具体的用处,是给HashMap的子类LinkedHashMap使用的,子类重写了该方法,但是HashMap类中该方法只是一个空实现,不信看代码
然后再返回旧节点的值,至此,存放过程总算是完成了。这一个流程看下来,发现底层代码写的真是优雅,只能羡慕了,痛恨自己写不出这样的代码。当然了,能够看懂也是很好的,不过实践中最重要的还是使用,面试的时候可能会让你背一背八股文,那也是没有办法的,还不如早日阅读源码,到时候背起来也能够得心应有一点。其实还有很多东西我没有讲到,比如扩容机制和树化过程,但是鉴于本人的水平以及篇幅,就只阐述这么多了,毕竟要去打球了,我很忙的(此处应有狗头)。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。