一不小心节约了 591 台机器!

15、木材与家具制造运营卓越性提升研究 本研究探讨了精益制造实践、精益文化与运营卓越之间的关系,聚焦于木材与家具制造行业。通过实证分析发现,精益制造实践不仅直接影响运营卓越,还通过培育以‘尊重员工’和‘持续改进’为核心的精益文化产生显著的间接作用。研究验证了LM实践与运营卓越正相关,且精益文化在其中起中介作用,为企业优化运营管理、推动文化变革提供了理论依据与实践指导。同时指出了样本局限性,并建议未来拓展研究范围,深入探索影响因素及长期效应。 阅读详情

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 

前段时间,我在 B 站上看到一个技术视频,题目叫做《机票报价高并发场景下的一些解决方案》。

up 主是 Qunar技术大本营,也就是我们耳熟能详的“去哪儿”。

视频链接在这里:

https://www.bilibili.com/video/BV1eX4y1F7zJ?p=2

当时其实我是被他的这个图片给吸引到了(里面的 12 qps 应该是 12k qps):

aa873c65a4bfba8d2e5bcf98471a3a74.png

他介绍了两个核心系统在经过一个“数据压缩”的操作之后,分别节约了 204C 和 2160C 的服务器资源。

共计就是 2364C 的服务器资源。

如果按照一般标配的 4C8G 服务器,好家伙,这就是节约了 591 台机器啊,你想想一年就节约了多大一笔开销。

48696db485672be64a0239852d30ecc7.png

视频中介绍了几种数据压缩的方案,其中方案之一就是用了高性能集合:

f663b133952f8a124fa91adfb5799260.png

因为他们的系统设计中大量用到“本地缓存”,而本地缓存大多就是使用 HashMap 来帮忙。

所以他们把 HashMap 换成了性能更好的 IntObjectHashMap,这个类出自 Netty。

为什么换了一个类之后,就节约了这么多的资源呢?

换言之,IntObjectHashMap 性能更好的原因是什么呢?

我也不知道,所以我去研究了一下。

1dccdcff5d42fd914c61651377982978.png

拉源码

研究的第一步肯定是要找到对应的源码。

你可以去找个 Netty 依赖,然后找到里面的 IntObjectHashMap。

我这边本地刚好有我之前拉下来的 Netty 源码,只需要同步一下最新的代码就行了。

但是我在 4.1 分支里面找这个类的时候并没有找到,只看到了一个相关的 Benchmark 类:

51a29901c7bb77d09da72c82db3766d7.png

点进去一看,确实没有 IntObjectHashMap 这个类:

7152653f99f43fabfc8030838478a715.png

很纳闷啊,我反正也没搞懂为啥,但是我直接就是一个不纠结了,反正我现在只是想找到一个 IntObjectHashMap 类而已。

4.1 分支如果没有的话,那么就 4.0 上看看呗:

f8fc2e04ec97b6f016bc263a6aec115b.png

于是我切到了 4.0 分支里面去找了一下,很顺利就找到了对应的类和测试类:

9e3015954aa417773e1938576060d970.png

能看到测试类,其实也是我喜欢把项目源码拉下来的原因。如果你是通过引入 Netty 的 Maven 依赖的方式找到对应类的,就看不到测试类了。

有时候配合着测试类看源码,事半功倍,一个看源码的小技巧,送给你。

而我要拉源码的最重要的一个目的其实是这个:

4fc16bd9ac7caebfe992f52c43f7fbc4.png

可以看到这个类的提交记录,观察到这个类的演变过程,这个是很重要的。

因为一次提交绝大部分情况下对应着一次 bug 修改或者性能优化,都是我们应该关注的地方。

比如,我们可以看到这个小哥针对 hashIndex 方法提交了三次:

eb75aec41b1a6d24882f167477a76c46.png

在正式研究 IntObjectHashMap 源码之前,我们先看看只关注 hashIndex 这个局部的方法。

首先,这个地方现在的代码是这样的:

eaa2931dd42885591fdb987bff2e109b.png

我知道这个方法是获取 int 类型的 key 在 keys 这个数组中的下标,支持 key 是负数的情况。

那么为啥这一行代码就提交了三次呢?

我们先看第一次提交:

92be7e7dec50d7db46ac51e1fc7a5855.png

非常清晰,左边是最原始的代码,如果 key 是负数的话,那么返回的 index 就是负数,很明显不符合逻辑。

所以有人提交了右边的代码,在算出 hash 值为负数的时候,加上数组的长度,最终得到一个正数。

很快,提交代码的哥们,发现了一个更好的写法,进行了一次优化提交:

f16aedd5e3a9e672f395dce70b0823f7.png

拿掉了小于零的判断。不管 key%length 算出的值是正还是负,都将结果加上一个数组的长度后再次对数组的长度进行 % 运行。

这样保证算出来的 index 一定是一个正数。

第三次提交的代码就很好理解了,代入变量:

所以,最终的代码就是这样的:

return (key % keys.length + keys.length) % keys.length;

这样的写法,不比判断小于零优雅的多且性能也好一点吗?而且这也是一个常规的优化方案。

如果你看不到代码提交记录,你就看不到这个方法的演变过程。我想表达的是:在代码提交记录中能挖掘到非常多比源码更有价值的信息。

又是一个小技巧,送给你。

3e091a44ab545b17c36f7abfdd0d8f44.png

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。

项目地址:https://github.com/YunaiV/ruoyi-vue-pro

IntObjectHashMap

接下来我们一起探索一下 IntObjectHashMap 的奥秘。

关于这个 Map,其实有两个相关的类:

cbbbc89982b92b8499ba642d78e9ce28.png

其中 IntObjectMap 是个接口。

它们不依赖除了 JDK 之外的任何东西,所以你搞懂原理之后,如果发现自己的业务场景下有合适的场景,完全可以把这两个类粘贴到自己的项目中去,一行代码都不用改,拿来就用。

在研究了官方的测试用例和代码提交记录之后,我选择先把这两个类粘出来,自己写个代码调试一下,这样的好处就是可以随时修改其中的源码,以便我们进行研究。

在安排 IntObjectHashMap 源码之前,我们先关注一下它 javadoc 里面的这几句话:

fef8b3b9e199b5e65cbdc6023f952749.png

第一句话就非常的关键,这里解释了 IntObjectHashMap 针对 key 冲突时的解决方案:

它对于 key 使用的是 open addressing 策略,也就是开放寻址策略。

为什么使用开放寻址呢,而不是采用和 HashMap 一样挂个链表呢?

这里也回答了这个问题:To minimize the memory footprint,也就是为了最小化内存占用。

怎么就减少了内存的占用呢?

这个问题下面看源码的时候会说,但是这里提一句:你就想想如果用链表,是不是至少得有一个 next 指针,维护这个东西是不是又得占用空间?

不多说了,说回开放寻址。

开放寻址是一种策略,该策略也分为很多种实现方案,比如:

  • 线性探测方法(Linear Probing)

  • 二次探测(Quadratic probing)

  • 双重散列(Double hashing)

从上面划线部分的最后一句话就可以知道,IntObjectHashMap 使用的就是 linear probing,即线性探测。

现在我们基本了解到 IntObjectHashMap 这个 map 针对 hash 冲突时使用的解决方案了。

接下来,我们搞个测试用例实操一把。代码很简单,就一个初始化,一个 put 方法:

7ac8f3da3ab4cfca2ddd32cb95463087.png

就这么几行代码,一眼望去和 HashMap 好像没啥区别。但是仔细一想,还是发现了一点端倪。

如果我们用 HashMap 的话,初始化应该是这样的:

HashMap<Integer,Object> hashMap = new HashMap<>(10);

你再看看 IntObjectHashMap 这个类定义是怎么样的?

只有一个 Object:

572d082f43a4577b89b44a2ae00eae9e.png

这个 Object 代表的是 map 里面装的 value。

那么 key 是什么,去哪儿了呢?是不是第一个疑问就产生了呢?

查看 put 方法之后,我发现 key 竟然就是 int 类型的值:

也就是这个类已经限制住了 key 就是 int 类型的值,所以不能在初始化的时候指定 key 的泛型了。

这个类从命名上也已经明确说明这一点了:我是 IntObjectHashMap,key 是 int,value 是 Object 的 HashMap。

那么我为什么用了个“竟然”呢?

因为你看看 HashMap 的 key 是个啥玩意:

b3f02871a0c75cd9eafc5094dfcdc6b0.png

是个 Object 类型。

也就是说,如果我们想这样初始化 HashMap 是不可以的:

01228cdf4688372980092cf27713ce4d.pngide 都会提醒你:老弟,别搞事啊,你这里不能放基本类型,你得搞个包装类型进来。

而我们平常编码的时候能这样把 int 类型放进去,是因为有“装箱”的操作被隐藏起来了:

29fe3ade2093dd439971023412147bf6.png

所以才会有一道上古时期的八股文问题:HashMap 的 key 可以用基本类型吗?

想也不用想,不可以!

key,从包装类型变成了基本类型,这就是一个性能优化的点。因为众所周知,基本类型比包装类型占用的空间更小。

接着,我们先从它的构造方法入手,主要关注我框起来的部分:

aa639debdd6c3dd643454c0057bc3d08.png

首先进来就是两个 if 判断,对参数合法性进行了校验。

接着看标号为 ① 的地方,从方法名看是要做容量调整:

fff4dc16afb2d4630e9d41997f87e12d.png

从代码和方法上的注释可以看出,这里是想把容量调整为一个奇数,比如我给进来 8 ,它会给我调整为 9:

cb71375f0c0e53f3f8bbc3f6ccfbcbe8.png

至于容量为什么不能是偶数,从注释上给了一个解释:

Even capacities can break probing.

意思是容量为偶数的时候会破坏 probing,即我们前面提到的线性探测。

额...

我并没有考虑明白为什么偶数的容量会破坏线性探测,但是这不重要,先存疑,接着往下梳理主要流程。

从标号为 ② 的地方可以看出这是在做数据初始化的操作。前面我们得到了 capacity 为 9,这里就是初始两个数组,分别是 key[] 和 values[],且这两个数组的容量是一样的,都是 9:

d4bb4e0b6baf945fc2f57008b3703d8b.png

两个数组在构造方法中完成初始化后,是这样的:

a63a40bdc25c595ce99ae0dcc6b4c7ad.png

构造方法我们就主要关注容量的变化和 key[]、values[] 这两个数组。

构造方法给你铺垫好了,接着我们再看 put 方法,就会比较丝滑了:

c8ff175698fc221436e244326bfca554.png

put 方法的代码也没几行,分析起来非常的清晰。

首先是标号为 ① 的地方,hashIndex 方法,就是获取本次 put 的 key 对应在 key[] 数组中的下标。

这个方法文章开始的时候已经分析过了,我们甚至知道这个方法的演变过程,不再多说。

daa661ffe566fb0086db8d7a961a57de.png

然后就是进入一个 for(;;) 循环。

先看标号为 ② 的地方,你注意看,这个时候的判断条件是 value[index] == null,是判断算出来的 index 对应的 value[] 数组对应的下标是否有值。

前面我专门强调了一句,还给你画了一个图:

key[] 和 values[] 这两个数组的容量是一样的。

为什么不先判断该 index 在 key[] 中是否存在呢?

可以倒是可以,但是你想想如果 value[] 对应下标中的值是 null 的话,那么说明这个位置上并没有维护过任何东西。key 和 value 的位置是一一对应的,所以根本就不用去关心 key 是否存在。

如果 value[index] == null 为 true,那么说明这个 key 之前没有被维护过,直接把对应的值维护上,且 key[] 和 values[] 数组需要分别维护。

假设以我的演示代码为例,第四次循环结束后是这样的:

52ffa439b3663bc8ec4d4e2bff010181.png

维护完成后,判断一下当前的容量是否需要触发扩容:

34bf2ebc863e5c8ea8c2d3283cb5d097.png

growSize 的代码是这样的:

0b128df8051b315143b4fb4d96955022.png

在这个方法里面,我们可以看到 IntObjectHashMap 的扩容机制是一次扩大 2 倍。

额外说一句:这个地方就有点 low 了,源码里面扩大二倍肯定得上位运算,用 length << 1 才对味儿嘛。

但是扩容之前需要满足一个条件:size > maxSize

size,我们知道是表示当前 map 里面放了几个 value 。

那么 maxSize 是啥玩意呢?

e3802e503b858a856e5f4c2de810b448.png

这个值在构造函数里面进行的初始化。比如在我的示例代码中 maxSize 就等于 4:

e9e61789daf785ed5ec5e454bf292f9a.png

也就是说,如果我再插入一个数据,它就要扩容了,比如我插入了第五个元素后,数组的长度就变成了 19:

7dd02251c97f9d68c66247d0fe0f6bce.png

前面我们讨论的是 value[index] == null 为 true 的情况。那么如果是 false 呢?

就来到了标号为 ③ 的地方。

判断 key[] 数组 index 下标处的值是否是当前的这个 key。

如果是,说明要覆盖。先把原来该位置上的值拿出来,然后直接做一个覆盖的操作,并返回原值,这个逻辑很简单。

但是,如果不是这个 key 呢?

说明什么情况?

是不是说明这个 key 想要放的 index 位置已经被其他的 key 先给占领了?

这个情况是不是就是出现了 hash 冲突?

出现了 hash 冲突怎么办?

3af420e8889653aac4d8c8dfdc947edf.png

那么就来到了标号为 ③ 的地方,看这个地方的注释:

Conflict, keep probing ... 冲突,继续探测 ...

继续探测就是看当前发生冲突的 index 的下一个位置是啥。

如果让我来写,很简单,下一个位置嘛,我闭着眼睛用脚都能敲出来,就是 index+1 嘛。

但是我们看看源码是怎么写的:

da7c357d1ce31c71f2e98a855842f94f.png

确实看到了 index+1,但是还有一个先决条件,即 index != values.length -1。

如果上述表达式成立,很简单,采用 index+1。

如果上面的表达式不成立,说明当前的 index 是 values[] 数组的最后一个位置,那么就返回 0,也就是返回数组的第一个下标。

要触发这个场景,就是要搞一个 hash 冲突的场景。我写个代码给你演示一下:

861c0ffc79da5a77fc5d329674927a95.png

上面的代码只有当算出来的下标为 8 的时候才会往 IntObjectHashMap 里面放东西,这样在下标为 8 的位置就出现了 hash 冲突。

比如 100 之内,下标为 8 的数是这些:

f5610bacaa5199778895adfa0407f970.png

第一次循环之后是这样的:

2bf27a2a99aacce911fc52aba1ce2097.png

而第二次循环的时候,key 是 17,它会发现下标为 8 的地方已经被占了:

7c2e216badebe99afd73defb243c2a65.png

所以,走到了这个判断中:

bfd4c6a31ac2915f32d7f922dd82a32d.png

返回 index=0,于是它落在了这个地方:

e0ed6efc3b7f0cef7bd937ff233885b6.png

看起来就是一个环,对不对?

是的,它就是一个环。

但是你再细细的看这个判断:

80cd90a5a65d63e8b77887cc6a75f377.png

每次计算完 index 后,还要判断是否等于本次循环的 startIndex。如果相等,说明跑了一圈了,还没找到空位子,那么就抛出 “Unable to insert” 异常。

有的朋友马上就跳出来了:不对啊,不是会在用了一半空间以后,以 2 倍扩容吗?应该早就在容量满之前就扩容了才对呀?

这位朋友,你很机智啊,你的疑问和我第一次看到这个地方的疑问是一样的,我们都是心思缜密的好孩子。

90d050abd3a098f07402a342e2f1ea6e.png

但是注意看,在抛出异常的地方,源码里面给了一个注释:

Can only happen if the map was full at MAX_ARRAY_SIZE and couldn't grow. 这种情况只有 Map 已经满了,且无法继续扩容时才会发生。

扩容,那肯定也是有一个上限才对,再看看扩容的时候的源码:

db32e2bc48dc0260c91b41a86c09aea3.png

最大容量是 Integer.MAX_VALUE - 8,说明是有上限的。

但是,等等,Integer.MAX_VALUE 我懂,减 8 是什么情况?

诶,反正我是知道的,但是咱就是不说,不是本文重点。你要有兴趣,自己去探索,我就给你截个图完事:

04b790aebdd96770f3acfdeb59e648ec.png

如果我想要验证一下 “Unable to insert” 怎么办呢?

这还不简单吗?源码都在我手上呢。

两个方案,一个是修改 growSize() 方法的源码,把最长的长度限制修改为指定值,比如 8。

第二个方案是直接严禁扩容,把这行代码给它注释了:

7ae1a056cb69c408ea3169a39c25e274.png

然后把测试用例跑起来:

1589ed25ba237eaf4de9363d8b00e357.png

你会发现在插入第 10 个值的时候,抛出了 “Unable to insert” 异常。

第 10 个值,89,就是这样似儿的,转一圈,又走回了 startIndex:

9a844f039cf19756151021c42a3b2ba3.png

满足这个条件,所以抛出异常:

(index = probeNext(index)) == startIndex

到这里,put 方法就讲完了。你也了解到了它的数据结构,也了解到了它的基本运行原理。

那你还记得我写这篇文章要追寻的问题是什么吗?

IntObjectHashMap 性能更好的原因是什么呢?

前面提到了一个点是 key 可以使用原生的 int 类型而不用包装的 Integer 类型。

现在我要揭示第二个点了:value 没有一些乱七八糟的东西,value 就是一个纯粹的 value。你放进来是什么,就是什么。

你想想 HashMap 的结构,它里面有个 Node,封装了 Hash、key、value、next 这四个属性:

a9fe722294461e2c43c0ce20f018517d.png

这部分东西也是 IntObjectHashMap 节约出来的,而这部分节约出来的,才是占大头的地方。

你不要看不起这一点点内存占用。在一个巨大的基数面前,任何一点小小的优化,都能被放大无数倍。

不知道你还记不记得《深入理解Java虚拟机》一书里面的这个案例:

079a357fede687cdfa7f5f54ff23f212.png

不恰当的数据结构导致内存在占用过大。这个问题,就完全可以使用 Netty 的 LongObjectHashMap 数据结构来解决,只需要换个类,就能节省非常多的资源。

道理,是同样的道理。

b380426e178b70eb2a7f6d1b5c5e1f3a.gif

基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。

项目地址:https://github.com/YunaiV/onemall

额外一个点

最后,我再给你额外补充一个我看源码时的意外收获。

b1589905c601a47061bf63322874d1f7.png

Deletions implement compaction, so cost of remove can approach O(N) for full maps, which makes a small loadFactor recommended. 删除实现了 compaction,所以对于一个满了的 map 来说,删除的成本可能接近 O(N) ,所以我们推荐使用小一点的 loadFactor。

里面有两个单词,compaction 和 loadFactor。

先说 loadFactor 属性,是在构造方法里面初始化的:

971ba9e0a1ee1cef15a3ef3ac3ad1fd1.png

为什么 loadFactor 必须是一个 (0,1] 之间的数呢?

首先要看一下 loadFactor 是在什么时候用的:

2d912072fd515865f9b371a4cd04c0cc.png

只会在计算 maxSize 的时候用到,是用当前 capacity 乘以这个系数。

如果这个系数是大于 1 的,那么最终算出来的值,也就是 maxSize 会大于 capacity。

假设我们的 loadFactor 设置为 1.5,capacity 设置为 21,那么计算出来的 maxSize 就是 31,都已经超过 capacity 了,没啥意义。

总之:loadFactor 是用来计算 maxSize 的,而前面讲了 maxSize 是用来控制扩容条件的。也就是说 loadFactor 越小,那么 maxSize 也越小,就越容易触发扩容。反之,loadFactor 越大,越不容易扩容。loadFactor 的默认值是 0.5。

接下来我来解释前面注释中有个单词 compaction,翻译过来的话叫做这玩意:

03f91823b244062245839edbe4ee8b96.png

可以理解为就是一种“压缩”吧,但是“删除实现了压缩”这句话就很抽象。

不着急,我给你讲。

我们先看看删除方法:

f5f42bc4711cc06bc5b090cb67627769.png

删除方法的逻辑有点复杂,如果要靠我的描述给你说清楚的话有点费解。

所以,我决定只给你看结果,你拿着结果去反推源码吧。

首先,前面的注释中说了:哥们,我推荐你使用小一点的 loadFactor。

那么我就偏不听,直接给你把 loadFactor 拉满到 1。

也就是说当这个 map 满了之后,再往里面放东西才会触发扩容。

比如,我这样去初始化:

new IntObjectHashMap<>(8,1);

是不是说,当前这个 map 初始容量是可以放 9 个元素,当你放第 10 个元素的时候才会触发扩容的操作。

诶,巧了,我就偏偏只想放 9 个元素,我不去触发扩容。且我这 9 个元素都是存在 hash 冲突的。

代码如下:

9a5b2b2ee3b98bd34978f99fc606461e.png

这些 value 本来都应该在下标为 8 的位置放下,但是经过线性探测之后,map 里面的数组应该是这个情况:

f044186d3b2797368d850fb5c8fdf493.png

此时我们移除 8 这个 key,正常来说应该是这样的:

22145d39344acfa10f22e4747f269187.png

但是实际上却是这样的:

387bd1acbf97a65835200b20c19456b4.png

会把前面因为 hash 冲突导致发生了位移的 value 全部往回移动。

这个过程,我理解就是注释里面提到的“compaction”。

上面程序的实际输出是这样的:

886ab9ce06719f4e12c98e4b3e33f5cd.png

符合我前面画的图片。

但是,我要说明的是,我的代码进行了微调:

18fbe08c73c348b01646505e4894f750.png

如果不做任何修改,输出应该是这样的:

450909de1021c0febc156aba8b4c83f5.png

key=8 并不在最后一个,因为在这个过程里面涉及到 rehash 的操作,如果在解释 “compaction” 的时候加上 reHash ,就复杂了,会影响你对于 “compaction” 的理解。

另外在 removeAt 方法的注释里面提到了这个东西:

72d70196a63e3962c7b92c106fbb1544.png

这个算法,其实就是我前面解释的 “compaction”。

我全局搜索关键字,发现在 IdentityHashMap 和 ThreadLocal 里面都提到了:

2b22b2bd6fa95187eff7bf7d1a39fb18.png

但是,你注意这个但是啊。

在 ThreadLocal 里面,用的是“unlike”。

ThreadLocal 针对 hash 冲突也用的是线性探测,但是细节处还是有点不一样。

不细说了,有兴趣的同学自己去探索一下,我只是想表达这部分可以对比学习。

02c77b0255a9640ade2dd9206382bd54.png

这一部分的标题叫做“额外一个点”。因为我本来计划中是没有这部分内容的,但是我在翻提交记录的时候看到了这个:

9100b2e9051eccc4e7590ef7ed730df4.png

https://github.com/netty/netty/issues/2659

这个 issues 里面有很多讨论,基于这次讨论,相当于对 IntObjectHashMap 进行了一次很大的改造。

比如从这次提交记录我可以知道,在之前 IntObjectHashMap 针对 hash 冲突用的是“双重散列(Double hashing)”策略,之后才改成线性探测的。

包括使用较小的 loadFactor 这个建议、removeAt 里面采用的算法,都是基于这次改造出来的:

ef5d12428c6e4552bfcd0e104693d937.png

引用这个 issues 里面的一个对话:

7d2d75184b47fbed0aacee4d5484d635.png

这个哥们说:I've got carried away,我对这段代码进行了重大改进。

在我看来,这都不算是“重大改进”了,这已经算是推翻重写了。

另外,这个“I've got carried away”啥意思?

英语教学,虽迟但到:

889e734ab0fe2ba72d47507d1e494e35.png

这个短语要记住,托福口语考试的时候可能会考。

21bf3d6f288ece163fadab4248f920eb.png

Netty 4.1

文章开始的地方,我说在 Netty 4.1 里面,我没有找到 IntObjectHashMap 这个东西。

其实我是骗你的,我找到了,只是藏的有点深。

3be607c689eb3b47b8ce91c8b9325037.png

其实我这篇文章只写了 int,但是其实基本类型都可以基于这个思想去改造,且它们的代码都应该是大同小异的。

所以在 4.1 里面用了一个骚操作,基于 groovy 封装了一次:

7adcf81fa67c26008a4191e353ec47c8.png

要编译这个模板之后:

253e86316118cabc24be877f69cf1977.png

才会在 target 目录里面看到我们想找的东西:

f499cac70a5040ecbd83521d6c56c0cc.png

但是,你仔细看编译出来的 IntObjectHashMap,又会发现一点不一样的地方。

比如构造方法里面调整 capacity 的方法变成了这样:

f35afc6cc83d3efa20d126337acebecd.png

从方法名称我们也知道这里是找一个当前 value 的最近的 2 的倍数。

等等,2 的倍数,不是一个偶数吗?

在 4.0 分支的代码里面,调整容量还非得要个奇数:

5caeaac5a25aff0a9368bd599af1c8f5.png

还记得我前面提到的一个问题吗:我并没有考虑明白为什么偶数的容量会破坏线性探测?

但是从这里又可以看出其实偶数的容量也是可以的嘛。

这就把我给搞懵了。

要是在 4.0 分支的代码中,adjustCapacity 方法上没有这一行特意写下的注释:

Adjusts the given capacity value to ensure that it's odd. Even capacities can break probing.

我会毫不犹豫的觉得这个地方奇偶都可以。但是他刻意强调了要“奇数”,就让我有点拿不住了。

算了,学不动了,存疑存疑!

8669e8ad4969a6dcbbdb0bd1db2fef4a.png

好了,那本文的技术部分就到这里啦。



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

c4cf7652e30ba72600eaab1dca8cd5ab.png

已在知识星球更新源码解析如下:

37a81780d31a7aeee2f132001ed661a3.png

9bc76d96e36cb561d096af5500f76580.png

8cb04b484b07993fa9a9c0e2914974b5.png

1d273dbd1ef09167ccff40b5501cc9b8.png

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
【信息科学与工程学】【数据科学】数据科学领域 第六篇 算法设计 01 算法设计 阅读详情

相关推荐

【信息科学与工程学】【安全领域】第三十五篇 网络安全算法表

编号算法名称算法的逐步骤思考推理的数学方程式和分解的数学方程式参数/变量列表复杂度(时间/空间)1​AES (高级加密标准)​1. 密钥扩展:从初始密钥生成轮密钥。 2. 初始轮: 3. 主轮循环(Nr-1轮):  - SubBytes:(非线性字节替换)  - ShiftRows:对状态矩阵的行进行循环移位。  - MixColumns:(在GF(2⁸)上的矩阵乘法)  - AddRoundKey: 4. 最终轮:执行SubBytes、ShiftRows、AddR

weixin_49199313的博客 1379

换个数据结构,一不小心节约了 591 台机器

前段时间,我在 B 站上看到一个技术视频,题目叫做《机票报价高并发场景下的一些解决方案》。up 主是 Qunar技术大本营,也就是我们耳熟能详的“去哪儿”。当时其实我是被他的这个图片给吸引到了(里面的 12 qps 应该是 12k qps):他介绍了两个核心系统在经过一个“数据压缩”的操作之后,分别节约了 204C 和 2160C 的服务器资源。共计就是 2364C 的服务器资源。如果按照一般标配的 4C8G 服务器,好家伙,这就是节约了 591 台机器啊,你想想一年就节约了多大一笔开销。

m0_74931226的博客 224

【信息科学与工程学】计算机科学与自动化——第十篇 芯片设计04 电路与时序模型数学模型(1)

​:CDN产品经理需在技术深度(架构/协议)、业务广度(行业场景/商业化)、软实力(决策/沟通)三方面均衡发展,并通过“数据驱动+场景化创新”持续优化产品。能力规划需围绕“体验-成本-可靠性”三角平衡展开,同时关注边缘计算与安全的未来融合趋势。

weixin_49199313的博客 975

又偷偷夹带反战私货!npm生态还能信任吗?

文| 罗奇奇出品 | OSC开源社区(ID:oschina2013)继百万周下载量的 npm 包“ node-ipc”以反战为名进行供应链投毒后,又一位开发者在代码中加入反战元素。3 月 17 日,俄罗斯开发人员 Viktor Mukhachev (aka Yaffle)在其流行的 npm 库“event-source- polyfill”中添加了一段反战代码。这段...

HollisChuang's Blog 611

【信息科学与工程学】【通信工程】第六十九篇 企业网络的数学分析02

编号类型领域子领域问题问题的数学分析及数值分析及算法分析及数学物理分析及数学化学分析及代数/拓扑/几何/结构/数论/逻辑/形式化证明参数列表及参数的数值范围关联知识和法律法规参考462企业园区网络园区网络监控需求WiFi体验劣化相关性分析(AI离群检测+相关性分析)逐步推理:用户规模U为WiFi用户数,流量规模F为体验KPI,时序特征T为体验劣化事件。AI使用离群检测(Isolation Forest)识别体验劣化用户,再用相关性分析(Pearson相关系数)找出与劣化最相关的网络指标。建立分析模型:劣化用

weixin_49199313的博客 907

【信息科学与工程学】计算机科学与自动化——第三十五篇 调度理论和调度算法01——资源调度算法

算法逻辑

weixin_49199313的博客 685

除阿里、网易和字节外,杭州居然还有这么多互联网公司!

今天带大家了解一下杭州的互联网公司,真的是不查不知道,一查吓一跳,杭州居然有这么多互联网公司,而且很多公司的总部都在杭州!本文将按照互联网公司、手机厂、硬件厂、垂直领域(独角兽)、国企和外企的顺序进行分享,并给出 2022 届的应届生薪资情况,这里薪资主要以开发岗为主。2022年校招薪资(数据来自网络,主要以小程序 offershow 爆料为主)互联网公司阿里阿里的总部设...

HollisChuang's Blog 7110

LSM6DSOW陀螺仪轮询驱动:从I2C/SPI配置到数据读取全解析

惯性测量单元(IMU)是现代嵌入式系统中实现姿态感知与运动跟踪的核心传感器,其通过加速度计和陀螺仪测量线性加速度与角速度。在嵌入式开发中,轮询(Polling)作为一种基础且可靠的通信方式,通过主循环主动查询传感器状态,简化了初始调试与验证流程。对于I2C/SPI接口的数字传感器,掌握寄存器配置、数据就绪标志检查及原始数据转换是通用的驱动方法,能够快速验证传感器工作状态并理解数据流。LSM6DSOW作为一款高性能6轴IMU,支持可配置的量程与输出数据速率,适用于无人机飞控、手持云台等高动态场景。本文以LSM

weixin_28728265的博客 557

用了Stream后,代码反而越写越丑?

往期热门文章:1、一不小心节约了 591 台机器! 2、你见过哪些目瞪口呆的 Java 代码技巧? 3、笑死!程序员延寿指南开源了 4、互联网黑话,被上海人翻译火了 5、还在用分页?太Low !试试 MyBatis 流式查询,真心爽!文章来源:【公众号:小姐姐味道】目录合理的换行舍得拆分函数合理的使用 Optional返回 Stream 还是返回 List?少用或者不用并...

Java后端技术 719

DFRobot MGC3130电容式手势传感器库深度解析

电容式近场感应(Electric Near Field Sensing)是一种非接触、抗干扰的三维空间感知技术,其原理基于人体介电特性对高频电场的扰动响应,具备低功耗、高鲁棒性与零物理接触等核心优势。该技术广泛应用于医疗人机交互、工业HMI、智能家电等对可靠性与环境适应性要求严苛的嵌入式场景。DFRobot_MGC3130库正是面向Microchip MGC3130芯片的工程化实现,封装了I²C驱动、ASU硬件加速调度及多层抽象API,支持手势识别、触控检测与接近唤醒等功能动态切换。结合电容式近场感应与嵌入

weixin_34620658的博客 550

2021秋招IC验证面经-ARM中国/中科芯/飞腾/地平线/中兴

中科芯 (央企福利待遇好,但是百分百不解决户口,适合当地人进去躺平。) 现场面试,三个面试官,时间大约40min 首先介绍一下自己,介绍一下项目。所做项目用到了FIFO吗,深度是多少,位宽是多少,仲裁算法是什么? 验证环境中的reference model是自己写的还是本身就有的,仲裁怎么在参考模型中实现,参考模型怎么获取寄存器的值? 怎么配置RTL寄存器,通过平台的寄存器模型进行寄存器配置,寄存器模型里边的方法,写一个寄存器或读一个寄存器怎么实现? 寄存器如何实现前门访问和后门访问,后门访问时

weixin_48014653的博客 6319

复旦博士用130行代码搞定核酸统计,2分钟解决人工一小时工作量

整理 | 于轩 出品 | 程序人生(ID:coder _life)这段时间以来,全国各地的疫情发展牵动人心。为了更好地配合疫情防控,复旦大学自三月初以来启动了常态化核酸筛查工作。这一工作要求辅导员挨个检查学生“健康云”核酸完成截图,确保“不漏一人”。听上去简单,做起来难。面对几十甚至上百张重复性高的截图,人工核查往往耗时耗力,一不小心还会看错看漏。为了解决这一...

HollisChuang's Blog 578

把 ST03 的历史数据留得更久:ABAP Workload 统计保留期设置、原理与落地避坑指南

SAP ST03性能数据保留期配置指南 ST03/ST03N是SAP系统性能分析的核心工具,但默认数据保留期通常较短(3个月左右),难以满足跨季度对比、年度规划等需求。本文详细解析了ST03数据保留期的配置方法及注意事项: 数据存储机制:性能数据存储在监控库中,通过定期重组作业清理过期数据 配置路径:7.0+系统在"Workload Collector Database→Reorganization→Control"中按Profile分别设置 关键限制:保留期最大值建议不超过99,否则可

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 218

Spring Boot性能太差,教你几招轻松搞定

往期热门文章:1、Fastjson 2 来了,性能继续提升,还能再战十年 2、一次线上MySQL分页事故,搞了半夜... 3、因为造轮子,我一个月就转正了 4、用了Stream后,代码反而越写越丑? 5、一不小心节约了 591 台机器! 文章来源:http://a.nxw.so/1biCvy目录异步执行增加内嵌 Tomcat 的最大连接数使用 @ComponentScan...

Java后端技术 718

AIGC时代的必备技能:提示词工程(Prompt Engineering)全面指南

本文主要介绍了AIGC时代的重要技能——提示词工程(Prompt Engineering)。文章首先定义了提示词的概念,强调其作为人类与AI沟通桥梁的重要性,并详细阐述了提示词的功能特性,包括输入、大模型处理和输出三个环节。接着,文章探讨了提示工程的重要性,如提高输出质量、节省资源和时间等。随后,文章深入分析了提示词的基本构成要素,包括指令、上下文、输入数据和输出指示符,并提出了提示词的五大基本原则:清晰性与具体性、提供充足的上下文、明确任务与目标、考虑受众和迭代思维。最后,文章介绍了提示词的七大常见技巧

weixin_43178406的博客 27万+

14、5G物理层的灵活解决方案:广义频分复用(GFDM)

广义频分复用(GFDM)作为一种高度灵活的5G物理层多载波调制方案,能够满足多样化应用场景的需求,包括高吞吐量的比特管道通信、低功耗高连接数的物联网、超低延迟的触觉互联网以及广覆盖的无线区域网。通过参数化配置,GFDM可适配LTE资源网格,实现与现有系统的兼容与共存。其支持多种解调方式、干扰消除技术、OQAM映射及预编码机制,兼具低带外发射、高频谱效率和强鲁棒性。GFDM还能统一描述OFDM、FBMC、FTN等多种经典与先进波形,成为5G及未来通信系统中极具潜力的通用波形框架。

gold8的专栏 108

一次线上MySQL分页事故,搞了半夜...

往期热门文章:1、因为造轮子,我一个月就转正了 2、用了Stream后,代码反而越写越丑? 3、一不小心节约了 591 台机器! 4、你见过哪些目瞪口呆的 Java 代码技巧? 5、笑死!程序员延寿指南开源了文章来源:【公众号:JAVA日知录】目录背景分析数据模拟测试解决方案小结今天给大家分享个生产事故,一个由于 MySQL 分页导致的线上事故,事情是这样的~背景一天晚上...

Java后端技术 282

爬虫瑞数5案例:联通

瑞数动态安全 Botgate(机器人防火墙)以“动态安全”技术为核心,通过动态封装、动态验证、动态混淆、动态令牌等技术对服务器网页底层代码持续动态变换,增加服务器行为的“不可预测性”,实现了从用户端到服务器端的全方位“主动防护”,为各类 Web、HTML5 提供强大的安全保护。

老衲爱洗头的博客 4470

Fastjson 2 来了,性能继续提升,还能再战十年

往期热门文章:1、一次线上MySQL分页事故,搞了半夜... 2、因为造轮子,我一个月就转正了 3、用了Stream后,代码反而越写越丑? 4、一不小心节约了 591 台机器! 5、你见过哪些目瞪口呆的 Java 代码技巧?FASTJSON 2.0是FASTJSON项目的重要升级,目标是为下一个十年提供一个高性能的JSON库,同一套API支持JSON/JSONB两种协议,...

Java后端技术 352

【图像去噪】基础知识之加噪 | 给图像加噪的若干种方式,包括加高斯白噪声(AWGN)、泊松-高斯噪声、模拟真实噪声(SIDD、DND)等

本文介绍给干净图像加噪的若干种方式,新手入门必看!。根据noise类型不同,产生不同的Noisy,Noisy一般是模型输入。【图像去噪】基础知识之图像流 | 图像从输入到输出的前世今生,包括类型变化、维度变化、数值变化等。

主要更新底层视觉(去噪、超分等)相关的科研内容,形式为【论文精读】+【论文复现】 1099

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

换个数据结构,IntObjectHashMap 内存优化

各种资源,各种教程,请=》https://www.itjpk.com/Artices?id=25 当时其实我是被他的这个图片给吸引到了(里面的 12 qps 应该是 12k qps): 他介绍了两个核心系统在经过一个“数据压缩”的操作之后,分别节约了 204C 和 2160C 的服务器资源。 共计就是 2364C 的服务器资源。 如果按照一般标配的 4C8G 服务器,好家伙,这就是节约了 591 台机器啊,你想想一年就节约了多大一笔开销。 视频中介绍了几种数据压缩的方案,其中方案之一就是用

lingdu257的专栏 1922

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

优化数据结构可以节省多少资源

研究数据结构

pscsx的专栏 297

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

labview教程

VI:在 LabVIEW(Laboratory Virtual Instrument Engineering Workbench,实验室虚拟仪器工程平台)中,VI 是 Virtual Instrument(虚拟仪器)的缩写。条件结构:相当于case语句,注意条件不允许为小数,若判断条件确实为小数需要×10的n次方得到整数,作为判断条件。兼容性:高版本lv可以打开低版本项目,低版本lv不能打开高版本项目,当前版本lv2016。参考书:《图像处理、分析与机器视觉》,图像处理专用参考书,全,不细。

weixin_43870003的博客 895

基于django+vue校园二手商品交易市场的设计与实现【开题报告+程序+论文】-计算机毕设

研究背景 随着高等教育普及率的提升,校园内学生数量激增,随之而来的是大量的二手商品需求与供给。学生们在毕业、转专业或日常学习中,常常会产生许多不再需要但仍具有使用价值的物品,如书籍、电子产品、生活用品等。同时,新生入学或学生间物品更新换代时,对二手商品的需求也日益旺盛。然而,传统的校园二手交易方式,如张贴广告、校园论坛发布信息等,存在信息分散、查找不便、交易效率低下等问题。因此,开发一个集物品分类、用户管理、二手商品展示与交易等功能于一体的校园二手商品交易市场,成为解决当前问题、提升校园资源利用效率的有效途

峰映学长-毕设项目 937
上一篇: Spring Boot + Prometheus + Grafana 打造可视化监控,一目了然!
下一篇: 面试必备:聊聊分布式锁的多种实现!
公众号-芋道源码
博客等级 码龄10年 1631粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值