hashmap 扩容是元素还是数组_曹工说JDK源码(1)--ConcurrentHashMap,扩容前大家同在一个哈希桶,为啥扩容后,你去新数组的高位,我只能去低位?...

如何计算,一对key/value应该放在哪个哈希桶

大家都知道,hashmap底层是数组+链表(不讨论红黑树的情况),其中,这个数组,我们一般叫做哈希桶,大家如果去看jdk的源码,会发现里面有一些变量,叫做bin,这个bin,就是桶的意思,结合语境,就是哈希桶。

这里举个例子,假设一个hashmap的数组长度为4(0000 0100),那么该hashmap就有4个哈希桶,分别为bucket[0]、bucket[1]、bucket[2]、bucket[3]。

现在有两个node,hashcode分别是1(0000 0001),5(0000 0101). 我们当然知道,这两个node,都应该放入第一个桶,毕竟1 mod 4,5 mod 4的结果,都是1。

但是,在代码里,可不是用取模的方法来计算的,而是使用下面的方式:

int entryNodeIndex = (tableLength - 1) & hash;

应该说,在tableLength的值,为2的n次幂的时候,两者是等价的,但是因为位运算的效率更高,因此,代码一般都使用位运算,替代取模运算。

下面我们看看具体怎么计算:

此处,tableLength即为哈希表的长度,此处为4. 4 - 1为3,3的二进制表示为:

0000 0011

那么,和我们的1(0000 0001)相与:

0000 0001 -------- 1

0000 0011 -------- 3(tableLength - 1)

相与(同为1,则为1;否则为0)

0000 0001 -------- 1

结果为1,所以,应该放在第1个哈希桶,即数组下标为1的node。

接下来,看看5这个hashcode的节点要放在什么位置,是怎么计算:

0000 0101 -------- 5

0000 0011 -------- 3(tableLength - 1)

相与(同为1,则为1;否则为0)后结果:

0000 0001 -------- 1

扩容时,是怎么对一个hash桶进行transfer的

此处,具体的整个transfer的细节,我们本讲不会涉及太多,不过,大体的逻辑,我们可以来想一想。

以前面为例,哈希表一共4个桶,其中bucket[1]里面,存放了两个元素,假设是a、b,其hashcode分别是1,5.

现在,假设我们要扩容,一般来说,扩容的时候,都是新建一个bucket数组,其容量为旧表的一倍,这里旧表为4,那新表就是8.

那,新表建立起来了,旧表里的元素,就得搬到新表里面去,等所有元素都搬到新表了,就会把新表和旧表的指针交换。如下:

java.util.concurrent.ConcurrentHashMap#transfer

private transient volatile Node[] nextTable;

transient volatile Node[] table;

if (finishing) {

// 1

nextTable = null;

// 2

table = nextTab;

// 3

sizeCtl = (tabLength << 1) - (tabLength >>> 1);

return;

}

1处,将field:nextTable(也就是新表)设为null,扩容完了,这个field就会设为null

2处,将局部变量nextTab,赋值给table,这个局部变量nextTab里,就是当前已经扩容完毕的新表

3处,修改表的sizeCtl为:假设此处tabLength为4,tabLength << 1 左移1位,就是8;tabLength >>> 1,右移一位,就是2,。8 - 2 = 6,正好就等于 8(新表容量) * 0.75。

所以,这里的sizeCtl就是,新表容量 * 负载因子,超过这个容量,基本就会触发扩容。

ok,接着说,我们要怎么从旧表往新表搬呢? 那以前面的bucket[1]举例,遍历这个链表,计算各个node,应该放到新表的什么位置,不就完了吗?是的,理论上这么写就完事了。

但是,我们会怎么写呢?

用hashcode对新bucket数组的长度取余吗?

jdk对效率的追求那么高,肯定不会这么写的,我们看看,它怎么写的:

java.util.concurrent.ConcurrentHashMap#transfer

// 1

for (Node p = entryNode; p != null; p = p.next) {

// 2

int ph = p.hash;

K pk = p.key;

V pv = p.val;

// 3

if ((ph & tabLength) == 0){

lowEntryNode = new Node(ph, pk, pv, lowEntryNode);

}

else{

highEntryNode = new Node(ph, pk, pv, highEntryNode);

}

}

1处,即遍历旧的哈希表的某个哈希桶,假设就是遍历前面的bucket[1],里面有a/b两个元素,hashcode分别为1,5那个。

2处,获取该节点的hashcode,此处分别为1,5

3处,如果hashcode 和 旧表长度相与,结果为0,则,将该节点使用头插法,插入新表的低位;如果结果不为0,则放入高位。

ok,什么是高位,什么是低位。扩容后,新的bucket数组,长度为8,那么,前面bucket[1]中的两个元素,将分别放入bucket[1]和bucket[5].

ok,这里的bucket[1]就是低位,bucket[5]为高位。

首先,大家要知道,hashmap中,容量总是2的n次方,请牢牢记住这句话。

为什么要这么做?你想想,这样是不是扩容很方便?

以前,hashcode 为1,5的,都在bucket[1];而现在,扩容为8后,hashcode为1的,还是在newbucket[1],hashcode为5的,则在newbucket[5];这样的话,是不是有一半的元素,根本不用动?

这就是我觉得的,最大的好处;另外呢,运算也比较方便,都可以使用位运算代替,效率更高。

好的,那我们现在问题来了,下面这句的原理是什么?

if ((ph & tabLength) == 0){

lowEntryNode = new Node(ph, pk, pv, lowEntryNode);

} else{

highEntryNode = new Node(ph, pk, pv, highEntryNode);

}

为啥,hashcode & 旧哈希表的容量, 结果为0的,扩容后,就会在低位,也就是维持位置不变呢?而结果不为0的,扩容后,位置在高位呢?

背后的位运算原理(大白话)

代码里用的如下判断,满足这个条件,去低位;否则,去高位。

if ((ph & tabLength) == 0)

还是用前面的例子,假设当前元素为a,hashcode为1,和哈希桶大小4,去进行与运算。

0000 0001 ---- 1

0000 0100 ---- 旧哈希表容量4

&运算(同为1则为1,否则为0)

结果:

0000 0000 ---- 结果为0

ok,这里算出来,结果为0;什么情况下,结果会为0呢?

那我们现在开始倒推,什么样的数,和 0000 0100 相与,结果会为0?

???? ???? ----

0000 0100 ---- 旧哈希表容量

&运算(同为1则为1,否则为0)

结果:

0000 0000 ---- 结果为0

因为与运算的规则是,同为1,则为1;否则都为0。那么,我们这个例子里,旧哈希表容量为 0000 0100,假设表示为2的n次方,此处n为2,我们仅有第三位(第n+1)为1,那如果对方这一位为0,那结果中的这一位,就会为0,那么,整个数,就为0.

所以,我们的结论是:假设哈希表容量,为2的n次方,表示为二进制后,第n+1位为1;那么,只要我们节点的hashcode,在第n+1位上为0,则最终结果是0.

反之,如果我们节点的hashcode,在第n+1位为1,则最终结果不会是0.

比如,hashcode为5的时候,会是什么样子?

0000 0101 ---- 5

0000 0100 ---- 旧哈希表容量

&运算(同为1则为1,否则为0)

结果:

0000 0100 ---- 结果为4

此时,5这个hashcode,在第n+1位上为1,所以结果不为0。

至此,我们离答案好像还很远。ok,不慌,继续。

假设现在扩容了,新bucket数组,长度为8.

a元素,hashcode依然是1,a元素应该放到新bucket数组的哪个bucket里呢?

我们用前面说的这个算法来计算:

int entryNodeIndex = (tableLength - 1) & hash;

0000 0001 ---- 1

0000 0111 ---- 8 - 1 = 7

&运算(同为1则为1,否则为0)

结果:

0000 0001 ---- 结果为1

结果没错,确实应该放到新bucket[1],但怎么推论出来呢?

// 1

if ((ph & tabLength) == 0){

// 2

lowEntryNode = new Node(ph, pk, pv, lowEntryNode);

}

也就是说,假设一个数,满足1处的条件:(ph & tabLength) == 0,那怎么推论出2呢,即应该在低位呢?

ok,条件1,前面分析了,可以得出:

这个数,第n+1位为0.

接下来,看看数组长度 - 1这个数。

数组长度

2的n次方

二进制表示

1出现的位置

数组长度-1

数组长度-1的二进制

2

2的1次方

0000 0010

第2位

1

0000 0001

4

2的2次方

0000 0100

第3位

3

0000 0011

8

2的3次方

0000 1000

第4位

7

0000 0111

好了,两个数都有了,

???????0??????? -- 1 节点的hashcode,第n + 1位为0

000000010000000 -- 2 老数组

000000100000000 -- 3 新数组的长度,等于老数组长度 * 2

000000011111111 -- 4 新数组的长度 - 1

运算:1和4相与

大家注意看红字部分,还有框出来的那一列,这一列为0,导致,最终结果,肯定是比2那一行的数字小,2这行,不就是老数组的长度吗,那你比老数组小;你比这一行小,在新数组里,就只能在低位了。

反之,如果节点的hashcode,这一位为1,那么,最终结果,至少是大于等于2这一行的数字,所以,会放在高位。

参考资料

原文:https://www.cnblogs.com/grey-wolf/p/13057567.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/507013.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ftp服务器文件名存在,有什么办法根据已知文件名来检测FTP服务器上是否存在该文件? 急...

蛊毒传说我不敢说_________________using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Net;using System.IO;namespace test{ class Program { static void Main(string[] args) { string path "ftp://192.168.1.200/ser…

A* 寻路 +寻路演示(js)

效果 每个单元格内文字&#xff1a; (F) (Price) (G) (H) 原理 原理是参考另一篇csdn博文&#xff0c;不过忘记收藏找不到了 初始化 open_set和 close_set。将 起点 加入open_set中&#xff0c;并设置优先级为0&#xff08;优先级最高&#xff09;。如果open_set不为空&…

pc端无法ping android模拟器_【内附下载方式】PC端最新宝可梦 Lets Go去皮去伊模拟器+最新dlc+mod...

游戏名称&#xff1a;精灵宝可梦 Let‘s go 皮卡丘 伊布游戏总大小&#xff1a;9GB游戏简介&#xff1a;《精灵宝可梦 Lets Go 皮卡丘/伊布》是宝可梦系列全新作品&#xff0c;也是该系列首次登上Switch主机&#xff0c;为后续作品的开发奠定了基础。本作可以视为是对第一世代红…

自定义处理网页选区字符并实时显示(js)

概述 浏览网页的时候&#xff0c;可能需要去数一下某段文字的字符数量&#xff0c;或者需要对选中内容做些实时翻译&#xff0c;比如进制的转化&#xff0c;可以使用脚本做个简单的实时翻译。 效果 选中内容&#xff0c;并且鼠标移动时会在左下角显示翻译后的结果。示例为翻…

css文本行高是哪个属性_CSS中的line-height行高属性的使用技巧小结

CSS中的line-hight属性是用来控制文本行之间的空隙的。它一般情况下没有单位的设定(如&#xff1a;line-height:1.4;)所以这是一个按字体尺寸的比例来计算。这对印刷版来说是一个很重要的属性。线条过低就会挤在一起&#xff0c;线条过高就会相距甚远&#xff0c;这两种情况都会…

模2运算及模2运算式计算 (js)

概述 学习二维码生成的时候卡在纠错码部分&#xff0c;然后就接触到了伽罗华域&#xff0c;了解到模2运算&#xff0c;恰好前不久刚了解了波兰表达式&#xff0c;就尝试写一个支持模2运算的算式解析计算。 结果 10011*101101.M2Calc() >"1011010" 涉及内容 模…

react配合python_部署React前端和Django后端的3种方法

Python部落(python.freelycode.com)组织翻译&#xff0c;禁止转载&#xff0c;欢迎转发。2020年4月7日星期二&#xff0c;马修西格尔类别&#xff1a;Django如果您要用Django REST开发web应用程序后端&#xff0c;并使用React或Vue开发应用程序前端。有很多方法实现。你需要做出…

伽罗瓦域(256) 生成指定纠错码字的生成多项式 (js)

效果 使用 复制code 保存为html 涉及内容 1、有限域、伽罗瓦域(256) 2、对数反对数 3、XOR 异或 两个因式各项相乘&#xff0c;当系数项相乘时&#xff0c;指数相加并mod(255) 合并同类项时&#xff0c;相同项的系数合并为 XOR操作&#xff1b; 伽罗瓦域依旧有些犯懵&…

python贪心算法求删数问题_贪心算法删数问题

删数问题给定n位正整数a&#xff0c;去掉其中任意k个数字后&#xff0c;剩下的数字按原次序排列组成一个新的正整数。对于给定的n和k&#xff0c;设计一个算法&#xff0c;找出剩下数字组成的新数最少的删数方案。输入示例&#xff1a; 178543 4输出&#xff1a; 13输入示例&am…

二维码-纠错码生成(js)

概要 本文主要为计算&#xff0c;而非对伽罗瓦域等数学内容的深入解析&#xff0c;在知道消息多项式以及纠错数量的情况下通过程序生成对应的纠错码。详细的二维码生成原理参考一个详细全面的二维码生成解析 效果 数据码: 长度16的示例数据码&#xff08;正常获取的是示例的…

一步一步学python爬虫_初学Python之爬虫的简单入门

初学Python之爬虫的简单入门一、什么是爬虫&#xff1f;1.简单介绍爬虫爬虫的全称为网络爬虫&#xff0c;简称爬虫&#xff0c;别名有网络机器人&#xff0c;网络蜘蛛等等。网络爬虫是一种自动获取网页内容的程序&#xff0c;为搜索引擎提供了重要的数据支撑。搜索引擎通过网络…

实现二维码-完整三种编码流程加代码解析(javascript)

效果 输入内容&#xff1a;XXXwedewed生日//&sss乐❤XXXwedewed生日//&sss乐❤ 完整的演示效果为&#xff0c;输入内容后会将解码绘制的每一步都展示&#xff08;有点长就不全截图了&#xff0c;可以直接移至最后复制代码到本地运行&#xff09;&#xff1a; 原理…

可视化排班管理_小白经理的思考日记-可视化看板和走动管理

前言&#xff1a;所谓勤于思&#xff0c;敏于行&#xff0c;实践能提升思考的深度&#xff0c;反思也能更好的指导实践。基于这样的认知&#xff0c;我将过往所学所思所行进行了专题整理&#xff0c;对内化知识和技能结构颇有益。又承蒙付老师抬爱和鼓励&#xff0c;推荐我看《…

headerIP php_PHP curl伪造IP地址和header信息代码实例

原标题&#xff1a;PHP curl伪造IP地址和header信息代码实例curl虽然功能强大&#xff0c;但是只能伪造$_SERVER["HTTP_X_FORWARDED_FOR"]&#xff0c;对于大多数IP地址检测程序来说&#xff0c;$_SERVER["REMOTE_ADDR"]很难被伪造&#xff1a;首先是clien…

c++ 使用nacos_为什么选用Nacos?虎牙直播微服务改造实践

原标题&#xff1a;为什么选用Nacos&#xff1f;虎牙直播微服务改造实践“相比文字和图片&#xff0c;直播提供了人与人之间更丰富的沟通形式&#xff0c;其对平台稳定性的考验很大&#xff0c;那么倡导“以技术驱动娱乐”的虎牙直播如何在技术上赋能娱乐&#xff1f;本文将分为…

three.js 拖动场景中物体(原生|拖拽控制器)

非控制器版 拖动场景中的物体实际上是在一个平行于窗口的平面中进行拖动&#xff0c;确定这个平面并确定鼠标在该平面中的位置变化&#xff0c;就将问题转换成简单的2d移动物体了 <!DOCTYPE html> <html> <head><meta charsetutf-8><title>015-…

bch纠错码 码长8_从HDMI视频数据带有BCH纠错码讨论线材对画质的影响

一直感觉几十元的HDMI线已经可以正常传输视频信号,那么几百元应该是接近完美了,再听人说换上几千块的线就让画质黑位什么的提升一大截,认为非常不可思议.但别人又信誓旦旦的说自己是感觉到了明显改变,对这种情况产生的争论最终不会得到正确答案,所以数据党还是从科学的角度先分…

element-ui 可复选树型表格

效果 思路 自定义模板当点击某个行复选框时&#xff0c;其所有后代复选框都要同步状态&#xff0c;且其直系父辈状态需要根据所点击复选框的状态来修正点击全选复选框时&#xff0c;批量同步所有行内复选框状态 代码 非封装组件&#xff0c;按需自行改写 确保引入element-ui…

linux 删除含有关键词的文件_linux下查找包含关键字的文件

在linux下如果要查找包含某个关键字的文件&#xff0c;如要在nagios目录下搜索带有关键字“store-rd-sys”的文件&#xff0c;在终端下运行命令&#xff1a;/usr/local/nagios/etc/* (nagios目录)法1&#xff1a;grep -r “关键字” 路径[rootnagios01 ~]#grep -r "store-…