【注】本文翻译自:JavahashCode()指南|BaeldungJavahashCode()指南1.概述散列是计算机科学中的一个基本概念。在Java中,高效的哈希算法支持一些最流行的集合,例如HashMap(查看这篇深入的文章)和HashSet。在本教程中,我们将重点介绍hashCode()的工作原理、它在集合中的处理方式以及如何正确实现它。2.在数据结构中使用hashCode()在某些情况下,最简单的集合操作可能效率低下。例如,这会触发线性搜索,这对于大型列表来说效率非常低:Listwords=Arrays.asList("Welcome","to","Baeldung");if(words.contains("Baeldung")){System.out.println("Baeldung在列表中");}Java提供了许多旨在处理此问题的数据结构。比如几个Map接口的实现都是哈希表(hashtables)。使用哈希表时,这些集合使用hashCode()方法计算给定键的哈希值。然后他们在内部使用这个值来存储数据,以便访问操作更有效率。3.了解hashCode()的工作原理简而言之,hashCode()返回一个由散列算法生成的整数值。相等的对象(根据它们的equals())必须返回相同的哈希码。不同的对象不需要返回不同的哈希码。hashCode()的一般契约规定:在Java应用程序执行期间,无论何时在同一个对象上多次调用它,hashCode()必须始终返回相同的值,前提是在对象的等号比较中使用的信息没有被修改。从应用程序的一次执行到同一应用程序的另一次执行,该值不需要保持一致。如果根据equals(Object)方法两个对象相等,则对这两个对象中的每一个调用hashCode()方法必须产生相同的值。如果根据equals(java.lang.Object)方法两个对象不相等,则对两个对象中的每一个调用hashCode方法不需要产生不同的整数结果。但是,开发人员应该意识到,为不相等的对象生成不同的整数结果可以提高哈希表的性能。“在合理可行的情况下,类Object定义的hashCode()方法确实会为不同的对象返回不同的整数。(这通常是通过将对象的内部地址转换为整数来实现的,但JavaTM编程语言不需要这样的实现技术。)"4.一个简单的hashCode()实现完全符合上述契约的简单hashCode()实现实际上非常简单。为了证明这一点,我们将定义一个示例用户类来覆盖此方法的默认实现:publicclassUser{privatelongid;私有字符串名称;私人字符串电子邮件;//标准的getters/setters/constructors@OverridepublicinthashCode(){return1;}@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;如果(o==null)返回false;如果(this.getClass()!=o.getClass())返回false;用户user=(User)o;返回id==user.id&&(name.equals(user.name)&&email.equals(user.email));}//gettersandsettershere}用户类为equals()和hashCode()提供自定义实现,完全遵守各自的约定。更重要的是,让hashCode()返回任何固定值并不违法。但是,此实现将哈希表的功能减少到基本上为零,因为每个对象都将存储在同一个桶中。在这种情况下,哈希表查找是线性执行的,并没有给我们任何真正的优势。我们将在第7节中详细讨论它。5.改进hashCode()实现让我们通过包含User类的所有字段来改进当前的hashCode()实现,以便它可以为不相等的对象产生不同的结果:@OverridepublicinthashCode(){return(int)id*name.hashCode()*email.hashCode();}这种基本的哈希算法肯定比前面的好很多。这是因为它只是通过将name和email字段的哈希码与id相乘来计算对象的哈希码。一般来说,我们可以说这是一个合理的hashCode()实现,只要我们与equals()实现保持一致即可。6.标准的hashCode()实现我们用来计算哈希码的哈希算法越好,哈希表的性能就越好。让我们看一个“标准”实现,它使用两个质数来为计算的哈希码增加更多的唯一性:@OverridepublicinthashCode(){inthash=7;hash=31*hash+(int)id;hash=31*hash+(name==null?0:name.hashCode());hash=31*hash+(email==null?0:email.hashCode());returnhash;}虽然我们了解hashCode()和equals()方法的作用很重要,但我们不必每次都从头开始实现它们。这是因为大多数IDE可以生成自定义hashCode()和equals()实现。从Java7开始,我们有一个Objects.hash()实用方法用于舒适的散列:Objects.hash(name,email)IntelliJIDEA生成以下实现:@OverridepublicinthashCode(){intresult=(int)(id^(编号>>>32));结果=31*结果+name.hashCode();结果=31*结果+email.hashCode();返回结果;}Eclipse产生这个:@OverridepublicinthashCode(){finalintprime=31;整数结果=1;结果=素数*结果+((email==null)?0:email.hashCode());结果=素数*结果+(int)(id^(id>>>32));result=prime*result+((name==null)?0:name.hashCode());returnresult;}除了上面基于IDE的hashCode()实现,它还可以自动生成一个高效的实现,例如使用Lombok..这种情况下,我们需要在pom.xml中添加lombok-maven依赖:org.projectlomboklombok-maven1.16.18.0pom现在用@EqualsAndHashCode注释User类就足够了:@EqualsAndHashCodepublicclassUser{//fieldsandmethodshere}我们为了生成hashCode()实现,我们在pom文件中包含了commons-langMaven依赖:commons-langcommons-lang2.6hashCode()可以这样实现:publicclassUser{publicinthashCode(){returnnewHashCodeBuilder(17,37).append(id).append(name).append(email).toHashCode();}一般来说,实现hashCode()时没有通用的方法。我们强烈推荐阅读EffectiveJava。约书亚布洛赫。它提供了实施高效哈希算法的详尽指南列表。请注意,所有这些实现都以某种形式使用数字31。这是因为31有一个不错的属性。它的乘法可以用移位代替,这比标准乘法更快:31*i==(i<<5)-i7。处理哈希冲突哈希表的固有行为带来了这些数据结构之一相关方面:即使使用高效的哈希算法,两个或多个对象也可能具有相同的哈希码,即使它们不相等。所以即使他们有不同的哈希表键,他们的哈希码也会指向同一个桶。这种情况通常称为散列冲突,有多种处理方法,每种方法各有优缺点。Java的HashMap使用单独的链式方法来处理冲突:**“当两个或多个对象指向同一个桶时,它们只是简单地存储在一个链表中。在这种情况下,哈希表是一个链表数组,每个对象链表中的桶索引追加相同的哈希值,最坏情况下,多个桶会绑定一个链表,链表中对象的检索将线性进行。**哈希碰撞方法简单说明了高效实现hashCode()的重要性。Java8为HashMap实现带来了有趣的增强。如果桶大小超过某个阈值,树图将取代链表。这允许实现O(logn)查找而不是悲观的O(n)。8.创建一个简单的应用程序现在我们将测试标准hashCode()实现的功能。让我们创建一个简单的Java应用程序,将一些用户对象添加到HashMap并使用SLF4J来记录一个每次调用该方法时都会向控制台发送消息。这是示例应用程序的入口点:publicclassApplication{publicstaticvoidmain(String[]args){Mapusers=newHashMap<>();用户user1=newUser(1L,"John","john@domain.com");用户user2=newUser(2L,"Jennifer","jennifer@domain.com");用户user3=newUser(3L,"Mary","mary@domain.com");users.put(user1,user1);users.put(user2,user2);users.put(user3,user3);if(users.containsKey(user1)){System.out.print("在集合中找到用户");}}}这是hashCode()的实现:publicclassUser{//...publicinthashCode(){inthash=7;hash=31*hash+(int)id;hash=31*hash+(name==null?0:name.hashCode());hash=31*hash+(email==null?0:email.hashCode());logger.info("hashCode()调用-计算哈希值:"+hash);返回散列;}}这里需要注意的是,每次在hashmap中存入一个对象,并使用containsKey()方法进行校验时,都会调用hashCode()并将计算的哈希码打印到控制台:[main]INFOcom.baeldung.entities.User-hashCode()called-Computedhash:1255477819[main]INFOcom.baeldung.entities.User-hashCode()called-计算哈希值:-282948472[main]INFOcom.baeldung.entities.User-hashCode()调用-计算哈希值:-1540702691[main]INFOcom.baeldung.entities.User-hashCode()调用-计算哈希值:1255477819Userfound在collection9。结论很明显,生成hashCode()的高效实现通常需要混合使用数学概念(即素数和任意数)、逻辑和基本数学运算。无论如何,我们可以高效地实现hashCode()而无需使用这些技术。我们只需要确保哈希算法为不相等的对象产生不同的哈希码,并且它与equals()的实现一致。与往常一样,本文中显示的所有代码示例都可以在GitHub上获得。