HashMap源码中值得学习的一些东西

简介:

HashMap是我们在Java中用来保存key-value值使用频率较高的一个工具,其本身是对数据结构哈希表的一个具体实现,本文主要是以源码为主来解决我们下面提出的问题

1、HashMap是如何将一个对象的hashCode转换为数组中的索引?
  2、当发生冲突时,HashMap是如何解决冲突的?
  3、HashMap在什么情况下会扩容?怎么扩容?
  4、HashMap的线程安全问题

一、HashMap是如何将一个对象的hashCode转换为数组中的索引?

首先我们看一下是如何获取hashcode的

 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  }

这段代码主要做了如下几下事情:
1、如果key值为null则将key值设置为0
2、根据object中的native方法获取到一个int型的hashcode
3、hashcode的二进制位移动16位
4、将本身的hashcode与位移后的hashcode进行了一个^运算,得出结果

步骤3和步骤四是将已经获取到的hashcode进行了一个高低位的干扰运算。
我们知道int类型是取值范围是-2147483648到2147483648,加起来大概40亿的映射空间。
直接用来当作索引的话hashmap的默认容量就需要40Y个了,但是我们知道hashmap的初始空间为16。
所以需要将得到的hashcode映射为数组中的索引,我们看下面代码:

    tab[i = (n - 1) & hash]

这个就是将hashcode转化为索引的方法,n为数组的长度。
& 运算后就是高位值全部归零,只使用低位值来保证得到的索引是不超过当前数组的长度的。
(这里也就说明了为什么扩容是扩2次幂,因为这样数组的长度减1之后得到的二进制位尾数都是1)

但是这样问题就来了,两个完全不一样的hashcode,但是低位相同就会发生冲突,所以就有了步骤三和步骤四。

我们看一下下面的计算过程:

h:              1111 1111 1111 1111 1111 0000 1110 1010
h >>> 16:       0000 0000 0000 0000 1111 1111 1111 1111
h ^ (h >>> 16): 1111 1111 1111 1111 0000 1111 0001 0101

这样将高低位都参与到运算中就避免了低位相同导致出现冲突的情况。

二、当发生冲突时,HashMap是如何解决冲突的?

首先我们看一下添加元素进哈希表的putVal方法一个流程图

Untitled Diagram.png

然后我们分析一下源码:

1、判断当前hash表的容量是否为空,为空则进行扩容

if ((tab = table) == null || (n = tab.length) == 0)
     n = (tab = resize()).length;

2、根据得到的索引判断该位置是否有元素,没有则保存

if ((p = tab[i = (n - 1) & hash]) == null)
     tab[i] = newNode(hash, key, value, null);

3、判断当前的key值和发生冲突位置的key值是否相等,相等稍后直接覆盖

 if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

        if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }

4、判断当前的元素是否为红黑树中的一个节点,是在添加到树中

else if (p instanceof TreeNode)
        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

5、遍历该链表上的元素,将当前元素保存到最后一位

else {
       for (int binCount = 0; ; ++binCount) {
             if ((e = p.next) == null) {
               p.next = newNode(hash, key, value, null);

6、当链表的长度超过8时,将链表转为红黑树

if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    treeifyBin(tab, hash);

上面步骤就是将元素添加到哈希表中的过程,其中3-4-5-6是就是解决冲突的方法。
红黑树是在JDK1.8引入进来了,为了解决链表过长导致的效率慢的问题

三、HashMap在什么情况下会扩容?怎么扩容?

首先是在put方法刚开始时,哈希表未初始化时则进行扩容,其次就是在添加完成后

if (++size > threshold)
     resize();

当前的数组长度已经大于threshold(数组长度*0.75)则进行扩容。

为什么不是当长度超过数组长度进行扩容呢?

我们知道,当数组所剩余的长度比我们的元素数量少很多时,
就会发生大量的元素冲突(也未必能填满数组),导致哈希表的效率变慢,
所以我们需要提前对数组进行扩容。默认的0.75的在效率和内存空间上的一个平衡点。

接下来我们看一下扩容里面的一段代码:

else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
         oldCap >= DEFAULT_INITIAL_CAPACITY)
         newThr = oldThr << 1; // double threshold

新数组的长度其实就是旧数组的长度*2,上面也解释了为什么是2。

四、HashMap的线程安全问题

HashMap本身是线程不安全的,在多线程环境下会导致丢失数据,在JDK1.8之前扩容操作还会死循环,CPU飙升100%。

JDK1.7中出现死循环的原因主要是链表在转移数据的过程中,多线程环境下出现环形链表导致的,在JDK1.8中已经使用双链表解决了。

但是在多线程环境下还是不要使用,也不用加同步(锁全部对象导致效率差),直接使用ConcurrentHashMap来解决

参考资料:

https://www.zhihu.com/question/20733617 @胖胖
美团点评技术团队Java8重新认识HashMap https://zhuanlan.zhihu.com/p/21673805

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

推荐阅读更多精彩内容