哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率。在Java的Object类中有一个方法:
public native int hashCode();
根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现。
hashCode作用
hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。
在Java集合中有两类,一类是List,一类是Set他们之间的区别就在于List集合中的元素师有序的,且可以重复,而Set集合中元素是无序不可重复的。对于Set而言我们要如何来保证元素不重复呢?
也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。
此时hashCode()派上了用场,它是一个本地方法,它的实现与本地机器有关,这里我们暂且认为他返回的是对象存储的物理位置(实际上不是,这里写是便于理解)。当我们向一个集合中添加某个元素,集合会首先调用hashCode方法,这样就可以直接定位它所存储的位置,若该处没有其他元素,则直接保存。若该处已经有元素存在,就调用equals方法来匹配这两个元素是否相同,相同则不存,不同则散列到其他位置(散列冲突问题)。这样处理,当我们存入大量元素时就可以大大减少调用equals()方法的次数,极大地提高了效率。
所以hashCode在上面扮演的角色为寻域(寻找某个对象在集合中区域位置)。
hashCode可以将集合分成若干个区域,每个对象都可以计算出他们的hash码,可以将hash码分组,每个分组对应着某个存储区域,根据一个对象的hash码就可以确定该对象所存储区域,这样就大大减少查询匹配元素的数量,提高了查询效率。
hashCode 契约
为了使你的类与其他基于哈希的集合或其他依赖哈希码的算法一起正常工作,所有 hashCode 的实现必须遵守一个简单的契约。
这个契约在 hashCode 方法的 JavaDoc 中进行了阐述。它可以大致的归纳为下面几点:
- 在一个运行的进程中,相等的对象必须要有相同的哈希码
- 请注意这并不意味着以下常见的误解:
- 不相等的对象一定有着不同的哈希码——错!
- 有同一个哈希值的对象一定相等——错!
这个契约允许不同的对象共享相同的哈希码,例如根据上图中的的描述,“A”和“μ”对象的哈希值就一样。在数学术语中,从对象到哈希码的映射不一定为内射或者双射。这是显而易见的,因为可能的不同对象的数量经常比可能的哈希吗的数量 (2^32)更大。
规则一:无论你何时实现 equals 方法,你必须同时实现 hashCode 方法
一个对象的 hashCode 方法需要与 equals 方法考虑同样的域。通过重写 equals 方法,你将申明一些对象与其他对象相等,但是原始的 hashCode 方法将所有的对象看做是不同的。所以你将会有不同哈希码的相同对象。这违背了契约1中“相等的对象必须要有相同的哈希码”。
规则二:永远不要把哈希码误用作一个key
因为根据契约3,具有相同hash的对象不一定相同
规则三:在分布式应用中不要使用哈希码
我们可以看到hashCode方法是一个本地方法,它的实现取决于所处的操作系统,所以在分布式系统中一个对象的hash值在不同的系统中可能会不一样。
最好的建议可能是:完全不使用哈希码,除非你自己创造了基于哈希的算法。
如何重写hashCode()
《Effective Java》中提出了一种简单通用的hashCode算法:
A、初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result = 17;
B、选取equals方法中用于比较的所有域(之所以只选择equals()中使用的域,是为了保证上述原则的第1条),然后针对每个域的属性进行计算:
- 如果是boolean值,则计算f ? 1:0
- 如果是byte\char\short\int,则计算(int)f
- 如果是long值,则计算(int)(f ^ (f >>> 32))
- 如果是float值,则计算Float.floatToIntBits(f)
- 如果是double值,则计算Double.doubleToLongBits(f),然后返回的结果是long,再用规则(3)去处理long,得到int
- 如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0
- 如果是数组,那么需要为每个元素当做单独的域来处理。java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上。
C、最后,把每个域的散列码合并到对象的哈希码中。
hash替代方案
信息摘要SHA1有时被用来标识对象(例如,git这样做)。这也是不安全吗?不。SHA1使用 160 位密钥,这使得冲突几乎是不可能的。即使有很多对象,在这个空间发生冲突的几率远远低于一颗流星撞到你正在执行程序的电脑的几率。