欢迎光临散文网 会员登陆 & 注册

Android面试杂记

2023-07-23 08:04 作者:balalaxiaoMolina  | 我要投稿

1.String 的 hashCode 为什么乘数是31?

Java 中的 hashCode 方法返回实例的散列值,这个方法主要是为了哈希表提供的,只要不改变equals方法中的比较信息,每次在同一个实例上调用hashcode方法,必须始终返回相同的整数。下一次运行时,整数不必保持一致。

通过equals 方法比较相等的两个实例,他们调用hashcode 得到的整数必须相同.通过equals 方法比较不相等的两个实例,他们调用hashcode 得到的整数,可以相等,但最好是不相同的,因为这有助于提高哈希表的性能。

  1. 素数性质:31是一个素数,素数有较好的特性,能够减少哈希冲突的概率,使得生成的哈希码分布更加均匀。在哈希表中,较好的分布可以减少链表长度,提高查找效率。

  2. 性能优势:在计算机中,乘法运算比较高效,尤其是2的幂次方,而31正好是2的幂次方减1(31 = 2^5 - 1),因此对于大多数的实现来说,乘以31相当于进行了移位和减法操作,比乘以其他任意的素数要快速。

  3. 简单性:使用31作为乘数是一种简化的做法。在实践中证明,它在大多数情况下都能产生不错的结果。      

2.  为什么重写的 equals 方法就要重写 hashcode 方法

hashCode方法本质就是一个哈希函数,这个不是我根据字面意思瞎猜的,而是Object类的作者说明的。Object类的作者在注释的最后一段的括号中写道:将对象的地址值映射为integer类型的哈希值。(如果对哈希函数定义不大理解的同学可以看我另外一篇文章:通俗地理解哈希函数)。牢牢把握哈希函数的定义有利于帮助我们理解接下来的内容。

我们看到,hashCode方法注释中列了个列表,列表中有三条注释,当前需要理解的大致意思如下:

  1. 一个对象多次调用它的hashCode方法,应当返回相同的integer(哈希值)。

  2. 两个对象如果通过equals方法判定为相等,那么就应当返回相同integer。

  3. 两个地址值不相等的对象调用hashCode方法不要求返回不相等的integer,但是要求拥有两个不相等integer的对象必须是不同对象。

对象与哈希值对应关系

我们看到,图中存在两种独立的情况:

  1. 相同的对象必然导致相同的哈希值。

  2. 不同的哈希值必然是由不同对象导致的。

也就是作者在hashCode方法注释上写明了的定义,实际上作者也就是在实现一个哈希函数,并且把哈希函数的定义写到注释里。

其实我们看到这里,就能明白一件事情了:equals方法与hashCode方法根本就是配套使用的。对于任何一个对象,不论是使用继承自Object的equals方法还是重写equals方法。hashCode方法实际上必须要完成的一件事情就是,为该equals方法认定为相同的对象返回相同的哈希值

Object类中的equals方法区分两个对象的做法是比较地址值,即使用“==”。而我们如若根据业务需求改写了equals方法的实现,那么也应当同时改写hashCode方法的实现。否则hashCode方法依然返回的是依据Object类中的依据地址值得到的integer哈希值。

这么说起来可能不是很好理解,我们代入到具体的例子--String类中好了。

String类中,equals方法经过重写,具体实现源码如下:

 public boolean equals(Object anObject) {
       if (this == anObject) {
           return true;
       }
       if (anObject instanceof String) {
           String anotherString = (String)anObject;
           int n = length();
           if (n == anotherString.length()) {
               int i = 0;
               while (n-- != 0) {
                   if (charAt(i) != anotherString.charAt(i))
                           return false;
                   i++;
               }
               return true;
           }
       }
       return false;
   }


String类equals方法的重写实现

通过源码我们能看到,String对象在调用equals方法比较另一个对象时,除了认定相同地址值的两个对象相等以外,还认定对应着的每个字符都相等的两个String对象也相等,即使这两个String对象的地址值不同(即属于两个对象)。

此时我们能想到的是,String类中对equals方法进行重写扩充了,但是如果此时我们不将hashCode方法也进行重写,那么String类调用的就是来自顶级父类Obejct类中的hashCode方法。即,对于两个字符串对象,使用他们各自的地址值映射为哈希值。也就是会出现如下情形:

创建两个地址值不同,字面量相同的字符串对象

也就是说,被String类中的equals方法认定为相等的两个对象拥有两个不同的哈希值(因为他们的地址值不同)。问题分析到这一步,原来的问题“为什么重写equals方法就得重写hashCode方法”已经结束了,它的答案是“因为必须保证重写后的equals方法认定相同的两个对象拥有相同的哈希值”。同时我们顺便得出了一个结论:“hashCode方法的重写原则就是保证equals方法认定为相同的两个对象拥有相同的哈希值”。

看到这里,我的内心甚至没有一点波澜--两个字面量相同的String对象哈希值不同怎么啦!Object类作者给我的叮嘱我不遵守又怎么啦!说到现在为止,上方提到的任何东西都没有对我的实际代码没有造成任何影响。实际上是这样吗?两个被认定为相同的对象拥有不同的哈希值没有造成不便或者bug吗?这就是我们接下来要进一步挖掘的问题:为什么要保证它们的哈希值相等呢?“hashCode方法返回的哈希值在语言中扮演了一个什么角色?”。

在java集合Map中,如果将equals重写相等,没有重写hashcode,会出现下面情况,

map.put("1",hello) map.put("1",world)  这样map中会有2个实例存在,而不是一个。   

几乎可以肯定地说,hashCode方法不仅仅是与equals配套使用的,它甚至是与Java集合配套使用的。同样地,类似的代码我们也能在HashTable中找到,就更不用提HashSet一类的集合了。

集合本身在我们日常的编码中就必不可少,所以我们以后为了代码不出问题还是乖乖地重写hashCode方法吧。不过好在一般我们为了集合的效率以及安全性,都会使用不可变的String,它已经将hashCode方法重写了,并且重写的是一个散列极为优秀的hashCode方法。










Android面试杂记的评论 (共 条)

分享到微博请遵守国家法律