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

简介: 换个数据结构,一不小心节约了 591 台机器! (中)

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

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

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

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

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

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

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

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

image.png

image.png

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

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

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

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

那么 maxSize 是啥玩意呢?

image.png

image.png

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

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

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

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

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

说明什么情况?

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

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

出现了 hash 冲突怎么办?


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

Conflict, keep probing ...

冲突,继续探测 ...

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

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

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

image.png

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

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

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

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

image.png

image.png

image.png

image.png

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

是的,它就是一个环。

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

image.png

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

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

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

image.png

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

Can only happen if the map was full at MAX_ARRAY_SIZE and couldn't grow.

这种情况只有 Map 已经满了,且无法继续扩容时才会发生。

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

image.png

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

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

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

image.png

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

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

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

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

image.png

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

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


image.png

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

(index = probeNext(index)) == startIndex

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

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

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

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

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

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

image.png

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

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

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

image.png


目录
相关文章
|
算法
换个数据结构,一不小心节约了 591 台机器! (下)
换个数据结构,一不小心节约了 591 台机器! (下)
85 0
换个数据结构,一不小心节约了 591 台机器! (下)
|
缓存 Java 测试技术
换个数据结构,一不小心节约了 591 台机器! (上)
换个数据结构,一不小心节约了 591 台机器! (上)
186 0
换个数据结构,一不小心节约了 591 台机器! (上)
|
17天前
|
消息中间件 存储 搜索推荐
深入理解栈和队列(二):队列
深入理解栈和队列(二):队列
29 0
|
1月前
【栈】数据结构栈的实现
【栈】数据结构栈的实现
|
1月前
|
存储
数据结构--栈和队列
数据结构--栈和队列
|
1月前
|
存储 算法 数据处理
数据结构从入门到精通——栈
栈,作为一种后进先出(LIFO)的数据结构,在计算机科学中扮演着重要的角色。它的特性使得它在处理函数调用、括号匹配、表达式求值等问题时具有得天独厚的优势。然而,如果我们跳出传统思维的束缚,会发现栈的用途远不止于此。
58 0
|
1月前
|
C语言
数据结构之栈详解(C语言手撕)
数据结构之栈详解(C语言手撕)
37 1
|
1月前
|
存储 算法
数据结构— —栈的基本操作(顺序栈和链栈)
数据结构— —栈的基本操作(顺序栈和链栈)
58 0
|
10天前
|
存储 算法 调度
数据结构期末复习(3)栈和队列
数据结构期末复习(3)栈和队列
17 0