这个Map你肯定不知道,毕竟存在感确实太低了。 (中)

简介: 这个Map你肯定不知道,毕竟存在感确实太低了。 (中)

hash 方法只有两行。但是这两行都非常的关键。

先看第一个 System.identityHashCode,这个是什么东西?

看看 API 上的解释:

image.png

就是对于一个对象,不管你有没有重写 hashCode 方法,该方法返回的值都是不会变化的。

看两个示例代码:

image.png

注意 Person 对象是没有重写 hashCode 方法的。

程序的最终输出结果是这样的:

image.png

我们分成三个部分去看,我们可以发现。

当对象(Person)没有重写 hashCode 方法的时候,他们的 hashCode 和 identityHashCode 是一样的。

即使对象(String)重写了 hashCode 方法,对于不同的对象,hashCode 值是一样的,但是 identityHashCode 可能是不一样的。

注意是“可能不一样”。因为 identityHashCode 的底层逻辑是基于一个伪随机数生成的。

这个特性特别有用,但是也别乱用。用错了,就是一个 bug。

比如在 identityHashMap 里面的使用就是一个正确的使用。至于错误的使用,我们稍后会讲。

经过前面的分析我们知道了:hash 方法中的第一行代码,对于 new 出来的相同对象的不同实例,不管是否重写 hashCode 方法,会产生不同的 identityHashCode。

可以说 System.identityHashCode 方法,是整个 identityHashMap 的基石。

然后再看这一行代码:

image.png

很多朋友第一眼看到位运算,心里就稍微有点抵触。

别这样,我带你分析一下,很简单的。

首先,我前面画图示意了 identityHashMap 的存储套路,说了:key 的下一个位置就是这个 key 的 value。

那么 key 的位置一定要是一个偶数。

这一点能不能跟上?跟不上你就多想想再往下看。

image.png

而 hash 方法就是计算 key 的位置。

所以,该方法的返回值一定是一个偶数。

这缜密的逻辑,是不是无懈可击。

假设 length 为 64 的话,那么这一行代码的目的是为了生成一个 0 到 63 之间的偶数。

0 到 63 之间的数,是 &(length-1) 保证的。这个没啥说的。

那么为什么一定会生成一个偶数呢?

h<<1 的最终结果肯定是一个偶数吧?

h<<8 的最终结果肯定也是一个偶数吧?

那么偶数减去偶数是一个什么数?

什么,你问我会不会溢出?

你管它溢出不溢出,就算它变成负数了,变成 0 了,它也是一个偶数呀!

image.png

偶数的二进制的最后一位是不是 0?

length-1 这个数的二进制最后一位不是 0 就是 1,对不对?

0 & 上 0 或者 1,是不是还是 0?

那不就对了。所以,最终结果肯定是一个偶数的。

经过前面的分析,我们知道了标号为 ① 的地方返回的 i 肯定是一个 0 到 len-1 之间的偶数:

image.png

返回的这个偶数 i,在标号为 ② 和 ③ 的地方都有用到。

标号为 ② 的地方是检查传进来的这个 key 是否在数组中已经存在了,也就是我们说的是否 hash 冲突。

如果没冲突,继续往下执行。

如果冲突了,且 value 值存在,就替换 value 值,然后返回。

如果冲突了,且 value 值不存在, i 值经过 nextKeyIndex 方法后也发生了变化。

下标 i 是怎么变化的呢?

假设我们来了一个 key=key2 的元素,经过 hash 计算后,对应数组下标为 2,但是该位置上已经有了一个 key1 ,那么就是发生了 hash 冲突:

image.png

发生冲突,i+2,也就是找到下一个偶数下标。

代码中是这样的体现的:

image.png

当 key2 的 identityHashCode 和 key1 一样,发生 hash 冲突之后,是这样存储的:

image.png

那势必会出现 i+2 的结果比 len 还长的情况:

image.png


你发现源码是怎么解决这个问题的吗?

这个 nextkeyIndex 这个方法首尾相接,它是一个圆啊:


image.png

这种情况,这个圆,画图是怎么体现的呢?

image.png

怎么样,是不是很骚。

执行到编号为 ③ 的地方,就很清晰了:

key 是放在 tab[i] 的位置的。

value 是放在 tab[i+1] 的位置的。

image.png

和我们画图的逻辑一致。



畅游源码-GET

接下来我们看看 get 方法:

image.png

标号为 ① 的地方,直接取到了对应的 key。

你注意这个地方,用的是 == 来判断对象是否相等,hashMap 用的是 equals 。

标号为 ② 的地方,是没有对应的 key,直接返回 null。

走到标号为 ③ 的地方,代表这个 key 发生过 hash 冲突。那么接着找下一个偶数位下标的 key。

比如我们这里的 key2:

image.png

整个过程还是非常清晰的。学习的时候可以和 hashMap 的 get 方法进行对比学习。

你会发现,思想是一个思想,但是解决方案是完全不同的解决方案。

目录
相关文章
|
Oracle Java 关系型数据库
使用了这个神器,让我的代码bug少了一半(下)
使用了这个神器,让我的代码bug少了一半(下)
使用了这个神器,让我的代码bug少了一半(下)
|
10月前
不是工作不好找,是你真的不行
不是工作不好找,是你真的不行
|
8月前
|
测试技术
代码为啥不能过度优化
代码为啥不能过度优化
42 0
|
安全 Windows
这5款软件虽然知名度不高,但不代表不好用
其实有许多工具,知名度不高,用的人也很少,不过并不代表它们不好用,小编励志做一个合格的搬运工,让大家都能用上好用的软件。
76 1
BeyondCompare4无限使用办法
BeyondCompare4无限使用办法
147 0
BeyondCompare4无限使用办法
|
算法 搜索推荐 程序员
再也不担心用不好二分法了,因为我找到了"作弊"的接口
导读:算法是程序的灵魂,而复杂度则是算法的核心指标之一。为了降低复杂度量级,可谓是令无数程序员绞尽脑汁、甚至是摧枯秀发。一般而言,若能实现对数阶的时间复杂度,算法效率往往就已经非常理想。而实现对数阶的常用思想莫过于二分。 二分常有,好用的二分并不常有。while条件是lo<hi还是lo<=hi?分支判断mid是+1还是-1还是仍然取值mid?最后return哪个值?如果目标序列不是严格递增又该怎么处理?想想都不禁让人敬而远之。幸运的是,在python语言中,已经内置了成熟的二分函数。
116 0
再也不担心用不好二分法了,因为我找到了"作弊"的接口
|
安全 Java 测试技术
使用了这个神器,让我的代码bug少了一半(上)
使用了这个神器,让我的代码bug少了一半
使用了这个神器,让我的代码bug少了一半(上)
|
存储 Dubbo Java
这个Map你肯定不知道,毕竟存在感确实太低了。 (上)
这个Map你肯定不知道,毕竟存在感确实太低了。 (上)
77 0
这个Map你肯定不知道,毕竟存在感确实太低了。 (上)
|
存储 负载均衡 Dubbo
这个Map你肯定不知道,毕竟存在感确实太低了。 (下)
这个Map你肯定不知道,毕竟存在感确实太低了。 (下)
112 0
这个Map你肯定不知道,毕竟存在感确实太低了。 (下)
|
存储 缓存 安全
ConcurrentHashMap 有十个提升性能的地方,你都知道吗?
如何在高并发下提高系统吞吐是所有后端开发者追求的目标,Java并发的开创者Doug Lea在Java 7 ConcurrentHashMap的设计中给出
ConcurrentHashMap 有十个提升性能的地方,你都知道吗?