这个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 方法进行对比学习。

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

目录
相关文章
|
存储 负载均衡 Dubbo
这个Map你肯定不知道,毕竟存在感确实太低了。 (下)
这个Map你肯定不知道,毕竟存在感确实太低了。 (下)
130 0
这个Map你肯定不知道,毕竟存在感确实太低了。 (下)
|
存储 Dubbo Java
这个Map你肯定不知道,毕竟存在感确实太低了。 (上)
这个Map你肯定不知道,毕竟存在感确实太低了。 (上)
96 0
这个Map你肯定不知道,毕竟存在感确实太低了。 (上)
|
6月前
|
Dart
Dart之集合详解(List、Set、Map)
Dart之集合详解(List、Set、Map)
|
6月前
|
存储 JavaScript 前端开发
JavaScript进阶-Map与Set集合
【6月更文挑战第20天】JavaScript的ES6引入了`Map`和`Set`,它们是高效处理集合数据的工具。`Map`允许任何类型的键,提供唯一键值对;`Set`存储唯一值。使用`Map`时,注意键可以非字符串,用`has`检查键存在。`Set`常用于数组去重,如`[...new Set(array)]`。了解它们的高级应用,如结构转换和高效查询,能提升代码质量。别忘了`WeakMap`用于弱引用键,防止内存泄漏。实践使用以加深理解。
79 3
|
3月前
|
Go 定位技术 索引
Go 语言Map(集合) | 19
Go 语言Map(集合) | 19
|
3月前
|
存储 前端开发 API
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
该文章详细介绍了ES6中Set和Map数据结构的特性和使用方法,并探讨了它们在前端开发中的具体应用,包括如何利用这些数据结构来解决常见的编程问题。
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
|
4月前
|
存储 安全 Java
java集合框架复习----(4)Map、List、set
这篇文章是Java集合框架的复习总结,重点介绍了Map集合的特点和HashMap的使用,以及Collections工具类的使用示例,同时回顾了List、Set和Map集合的概念和特点,以及Collection工具类的作用。
java集合框架复习----(4)Map、List、set
|
4月前
|
Java
【Java集合类面试二十二】、Map和Set有什么区别?
该CSDN博客文章讨论了Map和Set的区别,但提供的内容摘要并未直接解释这两种集合类型的差异。通常,Map是一种键值对集合,提供通过键快速检索值的能力,而Set是一个不允许重复元素的集合。
|
4月前
|
算法 Java 索引
【Java集合类面试四】、 描述一下Map put的过程
这篇文章详细描述了HashMap中put操作的过程,包括首次扩容、计算索引、插入数据以及链表转红黑树和可能的再次扩容。
【Java集合类面试四】、 描述一下Map put的过程
|
4月前
|
存储