HashMap&LinkedHashMap源码分析

引言

java集合框架图

       基于Java集合框架图,本文针对Map集合的主要实现类从实现原理、特点、核心功能实现细节角度进行分析总结,目的是深入的了解其性能特点和适应场景,合理的优化使用;学习它们的架构设计思想和算法思想,编写更高效的代码。

HashMap的结构特点?

       JDK中HashMap是一种将元素以Key-Value形式存储的非线程安全、自动扩容的集合。其特点是利用java对象的hashCode唯一性作为元素的key,基于数组结构,使用哈希函数(除留取余法,hash(key)%n,n为数组长度),将非固定长度的key,映射为数组下标。进而通过下标位置存储key对应的哈希值value。

       可以说没有数组就没有哈希表。HashMap中的HashTable存储的元素是单链表,利用单链表解决哈希冲突,HashMap的HashTable是一个单链表数组结构。

HashMap

HashMap的哈希存储及查找原理?

       首先来看下HashMap的key的hash计算方法

hash

       如图,h>>>16将32位int值保留高16位,使用java对象hashCode异或操作,目的是使key值更随机。

       然后,看下put()方法的具体实现,在此之前先了解下,Node结点实现:HashMap内部使用单链表来存储元素,其中hash/key/value均为链表结点的数据属性。而HashTable就是存储单链表的数组。

Node

其中627-628是在进行扩容判断,629判断当前结点不在hash表中,则创建结点放入HashTable中。(其中   (n -1) &hash 等价于 hash%n ,n为HashTable长度,利用二进制按位&操作代替取余操作来提高在HashTable中定位元素的查找效率)。如果当前结点在HashTable中存在,则通过key原值比较,进而判断HashTable中存在的结点和当前结点是否相同,如果不是相同结点,此时发生哈希冲突,则将当前结点也放入链表尾部即可。当链表长度大于规定阀值8时,JDK1.8将单链表转换为红黑树存储元素,从而避免极端情况下散列到HashTable一个桶内,造成单链表过长性能退化。

putVal

HashMap的初始化及其扩容机制?为什么每次扩容为旧容量的2倍,而不是1.5倍或其他倍数呢?

       HashMap初始默认容量为16,初始扩容因子为0.75,初始化容量阀值=容量*扩容因子,即16*0.75=12。

       扩容逻辑是:插入元素后,容量是否超过当前容量阀值。每次扩容为原容量的2倍,并重新分配内存,对旧hash表中的数据,重新计算hash值,并迁移到新的hash表中。也就是说当HashMap采用默认初始化后,新增元素超过12时,会进行第一次扩容,扩容后的容量为16*2。之后每次都扩容为原容量的2倍,也就是说HashMap的容量始终是2的N次幂。

       HashMap定位和存储元素在hash表中的位置都是通过hash(key)%n实现的(除留取余法)这种方式如果除数不是2的N次幂,则无法利用位&操作,散列函数性能会大打折扣。这正是HashMap每次扩容为旧容量的2倍的原因。

       由于HashMap扩容条件,会导致浪费其自身%25的存储空间,以牺牲空间的代价来降低哈希冲突的概率,以此达到空间换性能的做法。如果一个容器存储1万个元素,则需要扩容7次。那么如何降低扩容的性能损耗呢?如果事先能够预见数据量大小,可以指定容量的方式初始化固定容量的集合,以此降低不必要的被动扩容性能损耗。

       下面对HashMap存储1万个元素,分别使用默认构造方法和指定容量(Capacity=10390,不扩容)方式构造,进行JMH吞吐量测试。从下图测试结果反馈,第一条指定容量比第二条未指定容量,ops高出不少。可见频繁被动扩容性能消耗不小,指定容量减少扩容可以提升性能。

HashMapJMH

小结:

       HashMap基于数组支持按下标随机访问的特性,利用除留取余的哈希算法,将key映射到数组下标,并将key对应的value值存储到下标位置。HashMap采用链表法解决哈希冲突,同时引入红黑树优化链表过长导致的哈希碰撞攻击。通常情况下认为HashMap的查询、插入和删除操作的时间复杂度均为O(1),适合快速精确无序查找的场景。


LinkedHashMap特点?为什么散列表经常和链表一起使用?

       LinkedHashMap是通过双向链表和散列表这两种数据结构组合实现的。LinkedList中的“Linked”实际上不仅仅指它是通过链表法来解决哈希冲突的,它还指双向链表实现的缓冲LRU策略,使HashMap支持按插入顺序或操作顺序遍历的含义。

        虽然散列表作为一种动态数据结构其插入、删除、查找操作的性能较高,但散列表中的数据都是通过散列函数之后,无序存储的。也就是说它不支持按某种顺序查找元素,为了使其有序遍历。每次遍历都要先排序的话,那效率势必会很低。为了解决这个问题,通常将散列表和链表(或者跳表)一起使用。

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

推荐阅读更多精彩内容