(注 : 此blog主要是为了加深java 源码的认知度的记录
hashCode是一个返回hash(散列码)的方法,hash 就是用于区分对象的标志,就是类似于人类的基因,我们的母类Object 就拥有这样的hashCode方法来返回hash值,这个在java 集合类的Map中是核心,所以map玩的溜,就得搞清楚键值对,键值对的重点就是hash
了解了hash之后,我们再来看看java 类库中的一个比较特殊的基本数据类型 String
先来看看代码
1 /** 2 * hashCode 3 */ 4 package test; 5 6 /** 7 * @author Amory.Wang 8 * Question : 9 * 2017年9月4日下午7:18:35 10 */ 11 public class Test { 12 public static void main(String[] args) { 13 String s = new String("s"); 14 String t = new String("s"); 15 16 System.out.println(s.hashCode() == t.hashCode()); 17 System.out.println(s.hashCode()); 18 System.out.println(t.hashCode()); 19 } 20 }
第一个syso 打印的是什么?
有点java 基础的猿都应该知道s 和 t 是两个不同的对象,这一点的证明可以用 == 来打印可看,所以答案应该是false,但是然而,却是true
你可能会反问 :wtf?你不是说不同的对象有自己的hash吗,自己的基因吗,你在欺骗我吗?
其实不然,所以看看源码就知道,String 里面是实现了hashCode()的方法的, 附码如下:
1 /** 2 * Returns a hash code for this string. The hash code for a 3 * <code>String</code> object is computed as 4 * <blockquote><pre> 5 * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 6 * </pre></blockquote> 7 * using <code>int</code> arithmetic, where <code>s[i]</code> is the 8 * <i>i</i>th character of the string, <code>n</code> is the length of 9 * the string, and <code>^</code> indicates exponentiation. 10 * (The hash value of the empty string is zero.) 11 * 12 * @return a hash code value for this object. 13 */ 14 public int hashCode() { 15 int h = hash; 16 if (h == 0 && value.length > 0) { 17 char val[] = value; 18 19 for (int i = 0; i < value.length; i++) { 20 h = 31 * h + val[i]; 21 } 22 hash = h; 23 } 24 return h; 25 }
我标记的红色重点看到没有 ?! 先解释一下 在String 类中定义的hash 和 value 如下 :
/** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0
hash 的默认是 0 , value 是创建对象的时候输入的参数,所以上面就是 String 类中产生hash值的方法,再来读一读这个源码方法,很明显可以看出,hash主要的值是有val的值决定的,
h = 31 * h + val[i];
而val 就是我们传入的参数嘛,这下清楚了吧,也就是说,其实String的hashCode返回的hash值直接关键就是我们传入的参数值,参数值一样,ok,产生的hash就是一样的。
爱问问题的人(比如我,哈哈,开个玩笑)可能会问,这个hash的生成前面不是还有一个乘法的操作吗?而且乍一看还没有意义,因为定义了h默认为0,乘起来就是0啊?
嗯,java组织毕竟都是大神,怎么会容忍没有意义的代码,这里其实要涉及到了计算机基础的知识,关于乘法的操作,(简单的讲一下啊,毕竟要深入那可还是一本书的知识) 计算机的5大部件之一,运算器,(CPU是由运算器和控制器组成的)就是执行运算功能的,还没有智能到可以想人脑一样来计算乘法操作,而是由减法或者逻辑运算来代替的,所以这里的乘法,再翻译一下就是 这样的
31 * h == (h << 5) - h
为什么要做这个看上去多此一举的动作?具体自己百度,简单说就是这样产生的hash值冲突不会很多,更加容易定位。
如果你使用 31,33, 37,39 和 41 这几个数值,将其应用于 hashCode 的算法中,每一个数字对超过 50000 个英语单词(由两个 Unix 版本的字典的并集构成)产生的 hash 只会产生少于 7 个的冲突。知道了这个之后,Java 大多数的发行版均会使用这几个数值之一的事实对你也不会显得奇怪了。
某乎上复制的。。。
再来看下面的代码
/** * hashCode */ package test; /** * @author Amory.Wang * Question : * 2017年9月4日下午7:18:35 */ public class Test { public static void main(String[] args) { String s = new String("s"); String t = new String("s"); System.out.println(s.hashCode() == t.hashCode()); System.out.println(s.hashCode()); System.out.println(t.hashCode()); StringBuilder sb = new StringBuilder("s"); StringBuilder sb1 = new StringBuilder("s"); System.out.println(sb.hashCode() == sb1.hashCode()); System.out.println(sb.hashCode()); System.out.println(sb1.hashCode()); } }
这个sb 是不是相等的呢?stringBuilder是不是和String 一样的呢?答案显然是 不是的,因为StringBuilder里面没有重写hashCode的方法,所以直接调用的是我们伟大的母类的Object的hashCode的方法,这里面的实现就是根据对象储存的位置有关,你家在哪,你的hash就是啥,同样,一般的对象,也是如此,所以一般的对象hash都是不一样的,总结一下String 类就是想搞特殊。。。开个玩笑