阿了嗝欢的小白日记——HashMap(上)

(说明:因为我最近用的是JDK1.7 截图内容和大家实际接触的略有不同。不过整体相差不远,应该能看懂。)

记得刚自学SE的时候就偏爱集合,也经常从和种知识贴和公众号上看到集合的讲解,加上自己翻看源码,所以对集合也有了简单的初步认识。

所以,新博客的第一篇文章就给了我个人认为最重要的集合之一——HashMap。

源码先不翻了,我对源码的剖析不够细,论坛里有太多大牛讲解HashMap的源码的底层实现。

我就简单的说一下自己的理解和总结。


从基础说起:

HashMap是由键值对组成的双列集合。

一个key(键)对应一个value(值)

key是唯一的。

1、那么就来说一下key的唯一是如何实现的:

HashMap和List一样,底层是靠数组来实现的,不过要更高级一些。

HashMap由默认初始长度为16的节点数组table构成(节点中储存key,value和下一个节点的引用),当我们调用put()方法想HashMap中存入数据时,方法内部会根据key值计算出一个hash值,并通过hash值确定索要储存的数组索引(hash值的范围一定在table.length-1范围内)。 好吧还是简单截两张图吧,不然光说很难理解。

首先是put()方法的:


hash()方法:


indexFor()方法:


(各种数据和位移运算符请看客自行百度,很简单的运算,很容易看懂,后续文章中,我应该也会对运算符做一定的总结)


在确定索引位置之后,会将存储好key,value的节点对象存入对应的索引。(这句话需要注意一下,因为一会讲到HashMap的数据结构哈希表,会涉及到这里)


但大家应该发现了,再添加节点到数组中之前,有一段循环遍历并做if判断的操作,这段操作便是判断存入的key是否已经才存在,如果存在,则用新value覆盖之前的老value。如果不存在,则直接存入key,value键值对。

怎么样,一个key值唯一的简单解释,涉及到了这么多的知识点。不过看懂了之后,还是很过瘾的。


2、刚刚说到节点中储存的内容中提到了哈希表,下面第二个点简单说一下哈希表的构成。

我们的都知道,HashMap的地城结构是哈希表,那么哈希表又是什么呢,哈希表结构到底是如何实现的的?(这个问题在我刚接触哈希表也困扰了我很久,因为当时对节点和链表等概念都非常模糊)

简单的说,哈希表就是刚刚说的节点数组。

但数组给人的感觉也是链状的,“表”的实现还动用节点的结构。

刚刚说了节点中除了储存key,value。还有下一个节点的引用。

在解释如何确保key唯一的原理时,是通过计算存入key的hash值来确定存入索引i的,但不同key也可能计算出相同的索引i。这时,会产生冲突(碰撞)。所以,我们要用链表来解决。同索引下的节点对象通过next指向下一个节点对象,从而形成链表。新来的节点映射到冲突的索引位置时,只需要插入到对应的链表即可

值得注意的是:HashMap中的链表是单向链表。并且新节点对象的插入采用的是“头插法”。(简单比喻一下就是说新来的节点对象会“挤掉”旧的节点对象,占在数组的索引位置,并在内部指向旧的节点对象)至于为什么用“头插法”我也不清楚,据说是因为HashMap的开发人员认为后插入的节点对象(也就是键值对)被查找的可能性个更大(至于为什么被查找的可能性大就放在用“头插法”接下来马上说明)。阿欢自我理解:其实从key唯一角度来考虑,这样的解释也不无道理。改变key,value键值对中的value时,用的也是put()方法,也就是变相的储存。修改次数越多,被放在索引位置的几率越大,也越说明该键值对被重视的程度越大,也就是被查找利用的几率越大。哈哈哈,以上纯属来自一个双鱼座的个人想象和理解。看得懂的话可以互相分享一下想法。(这说说到的查找,也需大家注意一下,因为接下来我们就来介绍一下查找get()方法)

附赠一张哈希表的结构图:(摘自百度百科)


3、说完储存,自然要说一下查取

刚刚我们已经知道,哈希表由数组和链表构成。也就是说哈希表兼备数组的查找快和链表的增删快特点。(对应ArrayList和LinkedList)

直接上马!驾!

第一张是get()方法的源码,很好理解。重点是getEntry()这个方法。


接下来看一下getEntry() :(在这里我有一个疑问,在get方法中已经对key判过一次空了,为什么到getEntry中还要再判断一次呢?我个人的猜想是防止并发访问时,在get判断不为空后,在直接行getEntry判空之前,有其他线程对当前的key进行了删除操作或者对map进行了clean操作二导致报错。请大牛解答一下是否正确或者还有其他原因)


这个方法也很简单,先确定索要查询的table数组索引,然后从头结点开始遍历查找对应的key值并获取对应的value。(这里涉及到时间复杂度的问题,对于时间复杂度我也是有浅显的认识,大家可以查一下大牛们的资料。以后如果我对此有更深的认识也会写相应日记)

简单来说,因为查找是从头结点开始的,所以要查找的目标节点距离头结点越近,那么查到的速度就越快。现在回想一下刚刚为什么put()方法要用“头插法”来进行是不是也能理解了。



好了 就先简单说下存储和查取。

通过写这篇文章我发现。我真的需要好好提高自己的文笔水平了。表达能力也很差,希望大家能看懂。

因为是想写就写了,也没什么准备。看来再写文章还是先打一个简单的草稿,准备好画图工具比较好。

我也是小白一个(技术和文笔都是)。希望看客老爷们多多见谅,希望大家共同进步!

下篇文章继续介绍HashMap,重点说一下在自己使用和面试中常见的问题和原理。



————急需进步的阿欢

  


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值