开始白活
其实这节本来是想聊聊集合的,
但是发现在聊集合之前,
有件事儿是绕不开的,
那就是hashcode编码
Hash我们都知道:
Hash是散列的意思,
就是把任意长度的输入,
通过散列算法变换成固定长度的输出,
该输出就是散列值。
那hashcode呢?
这货到底是个啥?
前情回顾
凑巧我们
正好趁此次我们来回顾一下,
就是在我们没有重写toString()方法时,
直接调用原始的toString()会给我们返回;
【对象的类名称 并 拼接一个16进制】
的hashcode码
(如下图示):
这个内地地址的16机制字符串到底是个什么呢?
我怀着一颗好奇的心,
举着一双欠揍的手,
进行了字符串的转换,
得到的结果
(如下图示):
WHAT ???
特么这货是个啥?
怪我孤陋寡闻…
赶快转换成是10进制压压惊…
(如下图示):
顿时看着舒服多啦…
我们再次进入Object的toString()方法的源码,
(如下图示):
我们发现它是将
@后面hashCode()方法给toHexString()16进制加工了一下;
马上我们会想到,
如果直接调用hashCode()方法,
会得到什么呢?
我们打印一下看结果,
(如下图示):
有同学发现啦,
这不是跟刚才我们在转换工具里得到的值一样吗?
来对照看一下,
(如下图示):
现在是不是脑子好像明白点什么了?
但 仔细一琢磨...
是不是又发现啥也没明白 [捂嘴笑…]
源码浅析
别着急,我们接着往下走,
咱们还是延续上文的套路,
将每种类型的hashcode()函数的源码都调出来,
然后逐一进行分解,
分别是Integer、String、person(Object对象)先来看看Integer
(如下图示):
Integer 包装类,
就是返回值本身,
没什么可多说的。再来看Object类,
(如下图示):
咱们上文说过,
自定义的类都是继承自Object 类,
我们看到Object类使用native关键字,
说到native因为不是本篇的重心,
这里咱们只是简单扩展下,
让同学们知道这是啥咱们目的就达到啦,
当然有兴趣的同学可以自己查查资料,
深入了解一下;
在java源码中使用native关键字,
基本可以判断是调用了非java的代码接口,
例如java需要调用底层接口,
而底层实现的语言可能是使用C++、
或是使用C、或是使用VB等等开发的DLL动态库,
随用随调,避免重复造轮子。
我们都明白既然是非java的代码,
源码肯定是没有权限看到,
我们平时工作一样吗,
自己写了一个组件或者一个服务,
别人要想用,给他们开个口就可以了,
源码肯定不能开放给他们,
道理是一样的。
那么这里的大致意思:
就是Java通过Jvm告知操作系统(咱们现在使用的是Windows)底层
某某函数、某某方法…
我java现在需要用一下;
操作系统接到这个指令;
很通情达理的就给它返回相应结果…最后是String
因为Object咱们看不到源码,
再耗下去也没意思,
快速pass到下环节,
下面是String 类源码,
(如下图示):
这么一看String 类还是最有料的,
那么String的hashCode()函数到底说了个啥呢?
别着急…
我们下运行一下;
先看看结果;
(如下图示):
第一个是Integer类型的根据我们刚刚看到的源码,
符合我们预期,的确是输出了它本身;
第三个Object 调用的是操作系统的hashcode,
返回的十进制数值。通过上文咱们的分析,
看不到源码,也讲不出个所以然。。。
我们现在来着重看看剩到最后的String类型;
有同学可能有疑问;
明明我们给的字符串是的值是“a”;
怎么返回个“97”呢?
同学们可能会想到,
咱们上文刚刚验证过,
已经知道:
hashcode使用的是10进制来表示的;
那“a”的10进制是不是“97”呢?
我们查下ASCII码对照表
(如下图示):
据上图所示;
证明我们的猜测是正确的;
同学们注意啦:
字母大小写是敏感的,
大写和小写的十进制编码是不同的。
“a”的10进制就是用“97”来表示的;
为了更好进一步确认,
我们再换一个字符试一下;
(如下图示):
得出的结果:
[a 为 97 | b 为 98]
手动滚动上文的ASCII码对照表;
对比后;
再次证明是正确的;
完美贴合我们的预设。
卓见成效 ???
是不是发现很简单,
到这里是不是有点小激动,
hhhhhhhh….
稍安勿躁,
我们接着往下看:
这个时候我们将代码稍稍做下调整;
然后再来看一下,
(如下图示):
WHAT THE FU……(文明用语,人人有责,大家不要学博主)
你字符a输出的结果我懂,
你字符b输出的结果我也懂,
a和b这么在一块待着,
输出的这个货——我看不懂。
发生了什么?
输出的值怎么就变成了 “3105”了呢?
按照ASCII码的数值,不应该是“195(97+98)”吗?
那这个“3105”是怎么来的呢?
源码人力跟踪
别着急我们先分析以下源码(注意源码有注释),
如下:
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
*
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
*
* using {@code int} arithmetic, where {@code s[i]} is the
* ith character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
public int hashCode() {
// 起到缓存的效果, 防止反复计算 hashCode
int h = hash;
// 如果 h != 0, 说明已经计算过 hashCode 了, 直接返回缓存值即可
if (h == 0 && value.length > 0) {
char val[] = value;
// 计算的核心步骤是这个 for 循环
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
根据源码分析我们发现,
函数一进来;
就判断字符串(按照我们的例子就是[s])是否已经hash过(h!=0是否成立)啦,
就被缓存了,直接就返回啦,
如果没有,
就把字符串转换成字符(char[])数组,
进行循环计算;
因为咱们字符串的长度是2,会循环两次,第一次循环后的结果是:
h = 31 * h(默认值是0)+97(字符“a”的10进制表示)
也就是
h = 31 * 0 +97
h = 97 (这符合我们只给字符“a”时返回的结果)第二次循环的结果是:
h = 31 * h(通过第一次循环,此时h已被赋值为“97”) +98(字符“b”的10进制表示)
也就是
h =31 * 97 + 98
h = 3105 (正好是我们页面输出的值)
hashCode的作用
啰里啰嗦说了大半天,
那hashcode的作用是什么?
想要明白hashCode的作用,
那咱们必须要先从Java中的集合说起...
那将又是个不小的话题;
我们决定放在下一篇,
到时仔细说道说道...
要不先给大家看着张截图,
虽然不具有什么代表性,
但也多少能让同学们有点启发;
(如下图示):
看我画的箭头多好看,【一支穿云箭,千军万马来相见...】
hahahahahahahaha.....
再会...