HashMap源码分析

hashmap的分析网上优秀的文章有很多,个人比较推荐美团技术博客上关于hashmap的介绍。由于hashmap广为人知,我在这里就简单的分析一下,关于里面太深层次的算法也不懂。。。

我觉得要了解一个类的第一步一定是要阅读他的doc文档,当读完之后其实就对其已经有了个大概的了解了,基本的使用肯定是没有问题,接下来如果想深入的了解,则可以阅读其源码。因为hashmap的文档实在太长而且还是英文,这里就不贴出来了,希望大家有时间可以去读一下。

我们直接来看他的几个静态常量。

由上至下分别是:默认容量大小,最大容量,负载因子,链表转树的阈值,树转链表的阈值,转成树时的最小容量。我们一一分析他们的用途。

hashmap要求容量大小必须是2的幂次,这个跟他的hash算法有关。负载因子代表着hashmap的使用率,默认是0.75(为什么是0.75?文档中说保证了时间与空间上的平衡),默认情况下,16*0.75=12,即容量为16的hashmap只能存储12个元素。当hashmap中元素发生hash冲突时,有两种解决方案,分别是开放地址法(继续向后寻找空位置)和拉链法(形成链表),hashmap采用了第二种方案,而当元素冲突过多,会导致链表过长,这时get操作的时间复杂度就会上升为O(n),jdk采用了当链表过长将其转为红黑树的方式优化。

hashmap内部表示:

1.8之前,hashmap采用entry数组实现,1.8之后改成了使用node数组实现,这里我们只看下1.8中node类的实现。这个类比较简单,实现了Map.Entry包含kv对,这个kv即为我们存入hashmap中的kv

接下来我们看下hashmap中的一个重要的辅助方法,求hash的方法,这里需要注意的是当key为null的时候,hash值为0,这个地方在后面会用到。

hash计算方法

具体这方法为什么要这么设计,我觉得这篇博文https://blog.csdn.net/fan2012huan/article/details/51097331写的很详细,大家可以看一下。

接下来看一下hashmap的主要构造函数

我们可以主动指定负载因子和容量,注意对容量赋值的时候调用了tableSizeFor方法,该方法保证容量大小为2的幂次。这里说一下,我们知道,当hashmap中容量不足的时候会进行扩容,扩容涉及到元素的移动,链表的重组等,比较耗费性能,所以当我们预先可以估计map的容量大小时,初始化时最好可以指定容量,避免以后map频繁进行扩容影响效率。

get方法

get方法比较简单,这里主要分析一下他的分支条件。

第一个大的if:判断hashmap是否初始化以及容量是否为0,并通过(n - 1) & hash(通过此算法获取元素在node数组中的位置)取得first节点。那么什么是first节点呢?如果大家对上文有印象的话会记得hashmap处理hash冲突采用的是拉链法+红黑树,这个first就是这个拉链或者树的首节点。
第二个if:这个比较简单,通过hash与equals判断first是否为要寻找的对象
第三个if:判断是否存在hash冲突,再判断链表是否已经转为树,最后依次查找直到找到相应的对象。
最后:若以上条件不满足,返回null值。

注意下(n - 1) & hash,为什么可以用这个方法来取模判断元素在数组中位置呢,正常来说我们会用 hash%n来求位置,而当n为2的幂次时,hash%n = (n - 1) & hash,与操作比求余操作效率更高,这也是hashmap要求容量为2的幂次的原因。

put方法:

接上图

put方法相对于get比较复杂,也是hashmap中比较核心的一个方法。和get一样,主要分析下他的分支条件

第一个if:首先判断tab是否已经初始化,没有则进行初始化。

第二个if:通过hash值判断该元素在node数组中的位置是否存在元素,不存在则new一个。这里拓展一下hashmap中关于key为null的处理。我们知道hashmap中是可以存null的,那么他存到哪里呢?首先走第一个if创建node数组,接着来到第二个if,tab[i = (n -1) & hash],这里的hash是使用hash(key)方法算出来的,翻看上文可知该方法当key为null的时候返回0,那么此时的i = (n -1) & hash=0,则null值被放在了node数组下标为0的位置。

else:第一个if,如果所在node数组位置上已经有元素了,通过该比较hash与equals比较元素是否相等,相等则进行替换。else if,若已经转成了红黑树,则使用putTreeVal方法进行存放。else,若是链表结构,则选择继续加长链表(加长后若到达转换为树节点的门限值,则继续将其转换为树结构)

最后判断元素数量是否超过threshold,超过则需要进行扩容,扩容方法也是一个核心方法。

接着来看扩容方法

说到扩容,想必大家都听说过高并发下HashMap cpu100%的问题,这个问题其实在1.8中已经修复了。这里我们先来复习下1.8以前为什么会发生这个问题。

1.7的源码懒得下载了,网图

由代码可知,e的next赋值给了newTabl[I],这表示新元素总会被加入到链表的头部,即采用的是链表的头插法。具体为什么会导致cpu100%,网上有一幅图总结的很好,总之是产生了闭环。

网图

接下来来看1.8中的扩容方法:

这里我们注意,当oldCap>上文的类常量MAXIMUM_CAPACITY时,threshold会扩容至int的最大值。这里将新的cap与thr都变成原来的两倍。总之,第一张图片中的代码只是为了确定扩容后tab的threshold与cap。后面的部分是真正扩容的部分,用一张图说明

jdk保证扩容时,要么元素还待在原来的索引上,要么元素待在位于原索引+oldCap处,这个具体如何实现的这里就不详细说了,跟hashmap一些列的hash处理有关。所以呢代码中的loTail和loHead就是扩容后还在原地的链表,hiTail和hiHead就是扩容后位于原索引+oldCap的链表。从图中我们可以清晰的看出,这里链表的重构采用的是尾插法。

remove方法:

和get方法类似,如果直接找到相应的元素,将其赋值给node,如果未直接找到说明元素有可能位于链表与树节点后面,分别通过相应的方法找到对应元素赋值给node。最后再通过树或链表或数组方法将该元素移除。我说的比较啰嗦,方法逻辑还是比较简明易懂的。

clear方法:

清空方法比较简单,就是将node数组中的元素引用置为null,使其能够被GC回收。

为什么HashMap是非线程安全的?

HashMap的非线程安全主要位于put功能,多线程put时会引发元素丢失(线程1检查node数组该位置无元素,此时线程2在该位置放置上元素,线程1再放则把其覆盖),resize时会发生上述死循环的问题,HashTable是HashMap的线程安全版本,但是其在方法上使用synchronized关键字实现,推荐使用ConcurrentHashMap(需要注意一点的是,线程安全的集合只能保证原子操作是线程安全的,如果是类似这样的操作 if(存在) then put(val),也并不是线程安全的)

结尾

最后那,关于红黑树相关的操作没有贴出来,因为红黑树只有在上学的时候了解过,现在基本上都忘了,那个5大基本性质都记不全了。。。想详细了解的可以去翻翻算法导论。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容