共计 2290 个字符,预计需要花费 6 分钟才能阅读完成。
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 类中该办法只是一个空实现,不信看代码
而后再返回旧节点的值,至此,寄存过程总算是实现了。这一个流程看下来,发现底层代码写的真是优雅,只能艳羡了,痛恨本人写不出这样的代码。当然了,可能看懂也是很好的,不过实际中最重要的还是应用,面试的时候可能会让你背一背八股文,那也是没有方法的,还不如早日浏览源码,到时候背起来也可能得心应有一点。其实还有很多货色我没有讲到,比方扩容机制和树化过程,然而鉴于自己的程度以及篇幅,就只论述这么多了,毕竟要去打球了,我很忙的(此处应有狗头)。