看不懂来找我!JDK1.8 HashMap resize()

Title:JDK1.8 HashMap resize() loTail.next = e loTail = e

技术博客已迁移至个人页,欢迎查看 yloopdaed.icu

您也可以关注 JPP - 这是一个Java养成计划,需要您的加入。


title:JDK1.8 HashMap resize() loTail.next = e loTail = e

前言

HashMap扩容的过程包含了很多巧妙的思考,思想简单易懂,但是代码的实现真的让人折服!

今天快速浏览resize方法时,被两行代码绕住了:

image

可能是对于引用类型的理解不够深刻吧,这两行代码真的看了我一天!网上也很少有人分析这两行代码(可能因为太容易了吧)

最后我自己写了个Demo,算是把这个操作搞清楚了。因为是引用类型的指针指向问题,所以画图也不太好理解,只能自己写个Demo,一点点Debug分析过程。

相关代码在:Jpp /TailInsert类中

准备工作

准备一个简单的链表对象

static class Node<K, V> {
    K key;
    V value;
    Node<K, V> next;

    Node(K key, V value, Node<K, V> next) {
        this.key = key;
        this.value = value;
        this.next = next;
    }
    
}   

简化过程

先看下面的代码,理解这个过程:

Node a = null;
Node b = null;
Node c = new Node('c', 'c', null);
b = c;
a = c;
c = new Node(8,8, null);
a.next = c;
a = c;

代码1-5行

声明了a,b两个Node类型的引用,声明c指向一个Node@478对象

然后把a和b都指向了Node@478对象。如下图所示:

p1

代码 6行

创建了一个新的Node@479对象,将c引用指向这个对象。如下图所示:

p1

代码 7行

在Node@478中有一个next属性,a.next = c;表示将Node@478的next指向c引用指向的地址(即Node@479)

p1

代码 8行

a = c; 就是将a的引用重新指向c引用指向的地址(即Node@479)

p1

所以最终的结果就是:

a,c引用指向相同的Node@479对象

b指向Node@478对象,其中Node@478的next引用指向Node@479对象

image

理解了上面的过程,下面就非常容易了 ^ ^

创建链表

创建链表并插入10个元素(尾插)来模拟HashMap数组中某一个节点的链表。

JDK1.8中,链表长度超过 变树阈值 时会将链表变化成红黑树
这个变化需要满足两个条件:1 链表长度超过变树阈值 2 HashMap的size大于64

Node tab = new Node(0,0,null); // node链表
Node p = tab;

for (int i = 1; i <= 10; i++) {
    Node tail = new Node(i, i, null); // 最后一个节点
    if (p.next == null) {
        p.next = tail;
    }else { // 尾插
        Node e;
        while ((e = p.next) != null) {
            if (e.next == null) {
                e.next = tail;
                break;
            }
            p = e;
        };
    }
}

仿写resize核心代码

在HashMap扩容时有一个很巧妙的操作,就是数组长度扩容至原先两倍时,重新计算链表节点的插入角标会将原链表随机分布到新数组的两个位置:1 原来角标的位置 2 原角标+原数组长度的位置

而这个操作在JDK1.7和JDK1.8中的思想是相同的,不过实现方式略有不同:

假设:
现在有两个链表节点
他们的hash值低8位分别是 0110 1011 、1001 1011
扩容前数组长度为16,扩容长度变为32
扩容前他们都在数组角标11的位置

// JDK1.7 e.hash & (length-1)
// 第一个节点
    0110 1011
    0001 1111
& 0000 1011 角标11
// 第二个节点
    1001 1011
    0001 1111
& 0001 1011 角标27

// JDK1.8 (e.hash & oldCap) == 0
// 第一个节点
    0110 1011
    0001 0000
& 0000 0000 结果0,在原数组位置
// 第二个节点
    1001 1011
    0001 0000
& 0001 0000 结果1,在新数组位置

JDK1.7中,直接通过hash值与新数组长度减1 按位与 得到新角标

JDK1.8中,通过计算hash值对应的原数组位置是否为0,如果为0则插入tab[j],否则插入tab[j+oldCap]


我模拟的Demo中没有设计这么多复杂的数据,所以简化为节点value值的奇偶判断

Node loHead = null, loTail = null;
Node hiHead = null, hiTail = null;
Node next;
Node e = tab;
do {
    next = e.next;
    if ((int)e.value % 2 == 0) { // 进入次判断的节点为 0,2,4,6,8
        if (loTail == null)
            loHead = e;
        else
            loTail.next = e;
        loTail = e;
    }
    if ((int)e.value % 2 == 1) {
        if (hiTail == null)
            hiHead = e;
        else
            hiTail.next = e;
        hiTail = e;
    }
} while ((e = next) != null);

以取模结果等于0为例:

第一次进入,loHead = 0, loTail = 0。loTail和loHead指向相同的内存地址

第二次进入,loTail.next = 2会移动loHead.next指向 2。随后loTail = 2,即loHead.next和loTail指向相同的内存地址

第三次进入,loTail.next = 4也就是loHead.next.next指向4。随后loTail = 4,即loHead.next.next和loTail指向相同的内存地址

以此类推,如下图:

image

思考

上面就是我对 loTail.next = e; loTail = e; 这两行代码的理解。

进一步思考,为什么HashMap在 JDK1.7 的时候会选择头插法插入元素?

不考虑JDK1.7中resize时的循环引用问题,我认为头插法无论是从理解的角度,还是从代码实现的角度都更胜一筹。甚至还可能能稍微优化一些查询的速率。

就拿上面Demo中的尾插生成测试链表为例,我的写法是仿照JDK1.8的,中间要声明很多局部变量,所以你会看到 JDK1.8 的源码中有很多判断中赋值的操作。如果不这么写的话,估计JDK1.8的源码会再多几百行(本身实现了一套红黑树,代码量相较1.7膨胀了几乎一倍)

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