Hashcode总结

本文深入解析了Java中哈希码(hashcode)的概念及其在数据结构中的应用,特别是哈希表和哈希集的高效查找机制。

Object:



hash code是一种编码方式,在Java中,每个对象都会有一个hashcode,Java可以通过这个hashcode来识别一个对象。至于hashcode的具体编码方式,比较复杂(事实上这个编码是可以由程序员重载的),可以参考数据结构书籍。而hashtable等结构,就是通过这个哈希实现快速查找键对象。这是他们的内部联系,但一般编程时无需了解这些,只要知道hashtable实现了一种无顺序的元素排列就可以了。  两个对象值相同(x.equals(y) == true),则一定有相同的hash code。  因为:Hash,一般翻译做“散列”,也有直接音译为"哈希"的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。  以下是java语言的定义:   1) 对象相等则hashCode一定相等;  2) hashCode相等对象未必相等。  这也涉及到如何写自定义的hashCode方法的问题:必须符合以上条件。注意条件2中的未必。具体可参见java doc; Effective Java中有更详细论述。  补充一点个人简洁 hash 就是 类似于数学集合, 每一个键,k可以对应一个或多个值,对象就类似于值,所以“相同的对象”具有相同的键值,也就是hashCode;

1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
例如内存中有这样的位置
0 1 2 3 4 5 6 7
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
但如果用hashcode那就会使效率提高很多。
我 们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余 数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除8 求余数直接找到存放的位置了。

2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。
也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
那么。重写了equals(),为什么还要重写hashCode()呢?
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊
3。你要对A类排序,有两种方法,一种就是让A类实现comparabole结构并实现compareTo()方法,那么可以通过Collections.sort(List <A> list)对其进行排序
另一种方法:自己定义一个类B实现Comparator类并实现compare方法,
然后通过Collections.sort(List <A> list,B b)进行排序

hashCode() 是用来产生哈希玛的,而哈希玛是用来在散列存储结构中确定对象的存储地址的,(这一段在 Java编程思想 中讲的很清楚的)象util包中的 带 hash 的集合类都是用这种存储结构 :HashMap,HashSet, 他们在将对象存储时(严格说是对象引用),需要确定他们的地址吧, 而HashCode()就是这个用途的,一般都需要重新定义它的,因为默认情况下,由 Object 类定义的 hashCode 方法会针对不同的对象返回不同的整数,这一般是通过将该对象的内部地址转换成一个整数来实现的,现在举个例子来说, 就拿HashSet来说 ,在将对象存入其中时,通过被存入对象的 hashCode() 来确定对象在 HashSet 中的存储地址,通过equals()来确定存入的对象是否重复,hashCode() ,equals()都需要自己重新定义,因为hashCode()默认前面已经说啦,而equals() 默认是比较的对象引用,你现在想一下,如果你不定义equals()的话,那么同一个类产生的两个内容完全相同的对象都可以存入Set,因为他们是通过 equals()来确定的,这样就使得HashSet 失去了他的意义,看一下下面这个:


    public class Test {
    public static void main(String[] args) {
    HashSet set = new HashSet();
    for (int i = 0; i <= 3; i++){
    set.add(new Demo1(i,i));
    }
    System.out.println(set);
    set.add(new Demo1(1,1));
    System.out.println(set);
    System.out.println(set.contains(new Demo1(0,0)));
    System.out.println(set.add(new Demo1(1,1)));
    System.out.println(set.add(new Demo1(4,4)));
    System.out.println(set);
    }

    private static class Demo1 {
    private int value;

    private int id;

    public Demo1(int value,int id) {
    this.value = value;
    this.id=id;
    }

    public String toString() {
    return " value = " + value;
    }

    public boolean equals(Object o) {
    Demo1 a = (Demo1) o;
    return (a.value == value) ? true : false;
    }

    public int hashCode() {
    return id;
    }
    }
    }


你分别注释掉hashCode()和 equals()来比较一下他们作用就可以拉,关键要自己动手看看比较的结果你就可以记得很清楚啦

如果还不是很明确可以再看另一个例子:


    public final class Test {

    public static void main(String[] args) {
    Map m = new HashMap();
    m.put(new PhoneNumber(020, 12345678), "shellfeng");
    System.out.println(m.get(new PhoneNumber(020, 12345678)));
    }

    private static class PhoneNumber {
    /**
    * 区号
    */
    private short areaCode;

    /**
    * 扩展号
    */
    private short extension;

    public PhoneNumber(int areaCode, int extension) {
    this.areaCode = (short) areaCode;
    this.extension = (short) extension;
    }

    public boolean equals(Object o) {
    if (o == this) {
    return true;
    }
    if (!(o instanceof PhoneNumber)) {
    return false;
    }
    PhoneNumber pn = (PhoneNumber) o;
    return pn.extension == extension && pn.areaCode == areaCode;
    }

    /**
    * @see java.lang.Object#hashCode()
    * @return result就是我们得到的散列值,其实我们的计算过程可以多种,这里只不过是一个例子,需要你的灵活运用,使其接近你需要的理想结果
    */
    public int hashCode() {
    int result = 17;
    result = 37 * result + areaCode;
    result = 37 * result + extension;
    return result;
    }
    }
    }


还是那句话:你注释掉hashCode()比较一下他们作用就可以拉,关键要自己动手看看比较的结果你就可以记得很清楚啦

总结
hashCode() 方法使用来提高Map里面的搜索效率的,Map会根据不同的hashCode()来放在不同的桶里面,Map在搜索一个对象的时候先通过 hashCode()找到相应的桶,然后再根据equals()方法找到相应的对象.要正确的实现Map里面查找元素必须满足一下两个条件:
(1)当obj1.equals(obj2)为true时obj1.hashCode() == obj2.hashCode()必须为true
(2)当obj1.hashCode() == obj2.hashCode()为false时obj.equals(obj2)必须为false

Java中的集合(Collection)有两类,一类是List,再有一类是Set。你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。
那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是Object.equals方法了。
但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。
也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。
哈 希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。我们可以认为hashCode方法返回的就是对象存储的物理地址(实际可能并不是,例 如:通过获取对象的物理地址然后除以8再求余,余数几是计算得到的散列值,我们就认为返回一个不是物理地址的数值,而是一个可以映射到物理地址的值)。
这 样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直 接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列 其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。


改写equals时总是要改写hashCode
============================================================
java.lnag.Object中对hashCode的约定:

   1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
   2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
   3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。


有一个概念要牢记,两个相等对象的equals方法一定为true, 但两个hashcode相等的对象不一定是相等的对象。

所以hashcode相等只能保证两个对象在一个HASH表里的同一条HASH链上,继而通过equals方法才能确定是不是同一对象,如果结果为true, 则认为是同一对象不在插入,否则认为是不同对象继续插入。

Object的代码:
public String toString () {
    return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());
}

public boolean equals (Object o) {
    return this == o;
}

/**
* Answers an integer hash code for the receiver. Any two
* objects which answer <code>true</code> when passed to
* <code>.equals</code> must answer the same value for this
* method.
*
* @author        OTI
* @version        initial
*
* @return        int
*                    the receiver's hash.
*
* @see            #equals
*/
public native int hashCode();


从上面我们可以看到是否很可能Object.hashCode就是代表内存地址。下面我们来证明hashcode是不是真的就是Object的内存地址呢?实际上,hashcode根本不能代表object的内存地址。
-----------------------------------------
Object.hashCode不可以代表内存地址
----------------------------------------

package com.tools;

import java.util.ArrayList;

/**
* 此方法的作用是证明 java.lang.Object的hashcode 不是代表 对象所在内存地址。
* 我产生了10000个对象,这10000个对象在内存中是不同的地址,但是实际上这10000个对象
* 的hashcode的是完全可能相同的
*/
public class HashCodeMeaning {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        int numberExist=0;
      
        //证明hashcode的值不是内存地址
        for (int i = 0; i < 10000; i++) {
            Object obj=new Object();
            if (list.contains(obj.toString())) {
                System.out.println(obj.toString() +" exists in the list. "+ i);
                numberExist++;
            }
            else {
                list.add(obj.toString());
            }
        }
      
        System.out.println("repetition number:"+numberExist);
        System.out.println("list size:"+list.size());
      
        //证明内存地址是不同的。
        numberExist=0;
        list.clear();
        for (int i = 0; i < 10000; i++) {
            Object obj=new Object();
            if (list.contains(obj)) {
                System.out.println(obj +" exists in the list. "+ i);
                numberExist++;
            }
            else {
                list.add(obj);
            }
        }
      
        System.out.println("repetition number:"+numberExist);
        System.out.println("list size:"+list.size());
    }
}


Object类有两种方法来推断对象的标识:equals()和hashCode()。一般来说,如果您忽略了其中一种,您必须同时忽略这两种,因为两者 之间有必须维持的至关重要的关系。特殊情况是根据equals() 方法,如果两个对象是相等的,它们必须有相同的hashCode()值(尽管这通常不是真的)。

特定类的equals()的语义在Implementer的左侧定义;定义对特定类来说equals()意味着什么是其设计工作的一部分。Object提供的缺省实施简单引用下面等式:

public boolean equals(Object obj) { return (this == obj); }

在这种缺省实施情况下,只有它们引用真正同一个对象时这两个引用才是相等的。同样,Object提供的 hashCode()的缺省实施通过将对象的内存地址对映于一个整数值来生成。由于在某些架构上,地址空间大于int值的范围,两个不同的对象有相同的 hashCode()是可能的。如果您忽略了hashCode(),您仍旧可以使用System.identityHashCode()方法来接入这类缺 省值。

    忽略 equals() -- 简单实例

缺省情况下,equals()和hashCode()基于标识的实施是合理的,但对于某些类来说,它们希望放宽等式的定义。例如,Integer类定义equals() 与下面类似:

public boolean equals(Object obj) {
return (obj instanceof Integer
&& intValue() == ((Integer) obj).intValue());
}

在这个定义中,只有在包含相同的整数值的情况下这两个Integer对象是相等的。结合将不可修改的 Integer,这使得使用Integer作为HashMap中的关键字是切实可行的。这种基于值的Equal方法可以由Java类库中的所有原始封装类 使用,如Integer、Float、Character和Boolean以及String(如果两个String对象包含相同顺序的字符,那它们是相等 的)。由于这些类都是不可修改的并且可以实施hashCode()和equals(),它们都可以做为很好的散列关键字。

为什么忽略 equals()和hashCode()?

如果Integer不忽略equals() 和 hashCode()情况又将如何?如果我们从未在HashMap或其它基于散列的集合中使用Integer作为关键字的话,什么也不会发生。但是,如果 我们在HashMap中使用这类Integer对象作为关键字,我们将不能够可靠地检索相关的值,除非我们在get()调用中使用与put()调用中极其 类似的Integer实例。这要求确保在我们的整个程序中,只能使用对应于特定整数值的Integer对象的一个实例。不用说,这种方法极不方便而且错误 频频。

Object的interface contract要求如果根据 equals()两个对象是相等的,那么它们必须有相同的hashCode()值。当其识别能力整个包含在equals()中时,为什么我们的根对象类需 要hashCode()?hashCode()方法纯粹用于提高效率。Java平台设计人员预计到了典型Java应用程序中基于散列的集合类 (Collection Class)的重要性--如Hashtable、HashMap和HashSet,并且使用equals()与许多对象进行比较在计算方面非常昂贵。使所 有Java对象都能够支持 hashCode()并结合使用基于散列的集合,可以实现有效的存储和检索。

==============================
Go deep into HashCode:
==============================

为什么HashCode对于对象是如此的重要?
一个对象的HashCode就是一个简单的Hash算法的实现,虽然它和那些真正的复杂的
Hash算法相比还不能叫真正的算法,但如何实现它,不仅仅是程序员的编程水平问题,
而是关系到你的对象在存取时性能的非常重要的问题.有可能,不同的HashCode可能
会使你的对象存取产生,成百上千倍的性能差别.

我们先来看一下,在JAVA中两个重要的数据结构:HashMap和Hashtable,虽然它们有很
大的区别,如继承关系不同,对value的约束条件(是否允许null)不同,以及线程安全性
等有着特定的区别,但从实现原理上来说,它们是一致的.所以,我们只以Hashtable来
说明:

在java中,存取数据的性能,一般来说当然是首推数组,但是在数据量稍大的容器选择中,
Hashtable将有比数据性能更高的查询速度.具体原因看下面的内容.

Hashtable在存储数据时,一般先将该对象的HashCode和0x7FFFFFFF做与操作,因为一个
对象的HashCode可以为负数,这样操作后可以保证它为一个正整数.然后以Hashtable的
长度取模,得到该对象在Hashtable中的索引.

index = (o.hashCode() & 0x7FFFFFFF)%hs.length;
这个对象就会直接放在Hashtable的第index位置,对于写入,这和数组一样,把一个对象
放在其中的第index位置,但如果是查询,经过同样的算法,Hashtable可以直接从第index
取得这个对象,而数组却要做循环比较.所以对于数据量稍大时,Hashtable的查询比数据
具有更高的性能.

既然可以根据HashCode直接定位对象在Hashtable中的位置,那么为什么Hashtable
要用key来做映射呢(为了一些思维有障碍的人能看到懂我加了一句话:而不是直接放value呢)?这就是关系Hashtable性能问题的最重要的问题:Hash冲突.

常见的Hash冲突是不同对象最终产生了相同的索引,而一种非常甚至绝对少见的Hash冲突
是,如果一组对象的个数大过了int范围,而HashCode的长度只能在int范围中,所以肯定要
有同一组的元素有相同的HashCode,这样无论如何他们都会有相同的索引.当然这种极端
的情况是极少见的,可以暂不考虑,但对于相同的HashCode经过取模,则会产中相同的索引,
或者不同的对象却具有相同的HashCode,当然具有相同的索引.

所以对于索引相同的对象,在该index位置存放了多个对象,这些值要想能正确区分,就要依
靠key本身和hashCode来识别.

事实上一个设计各好的HashTable,一般来说会比较平均地分布每个元素,因为Hashtable
的长度总是比实际元素的个数按一定比例进行自增(装填因子一般为0.75)左右,这样大多
数的索引位置只有一个对象,而很少的位置会有几个对象.所以Hashtable中的每个位置存
放的是一个链表,对于只有一个对象的位置,链表只有一个首节点(Entry),Entry的next为
null.然后有hashCode,key,value属性保存了该位置的对象的HashCode,key和value(对象
本身),如果有相同索引的对象进来则会进入链表的下一个节点.如果同一个位置中有多个
对象,根据HashCode和key可以在该链表中找到一个和查询的key相匹配的对象.

从上面我看可以看到,对于HashMap和Hashtable的存取性能有重大影响的首先是应该使该
数据结构中的元素尽量大可能具有不同的HashCode,虽然这并不能保证不同的HashCode
产生不同的index,但相同的HashCode一定产生相同的index,从而影响产生Hash冲突.

对于一个象,如果具有很多属性,把所有属性都参与散列,显然是一种笨拙的设计.因为对象
的HashCode()方法几乎无所不在地被自动调用,如equals比较,如果太多的对象参与了散列.
那么需要的操作常数时间将会增加很大.所以,挑选哪些属性参与散列绝对是一个编程水平
的问题.

从实现来说,一般的HashCode方法会这样:

return Attribute1.HashCode() + Attribute2.HashCode()...[+super.HashCode()],

我们知道,每次调用这个方法,都要重新对方法内的参与散列的对象重新计算一次它们的
HashCode的运算,如果一个对象的属性没有改变,仍然要每次都进行计算,所以如果设置一
个标记来缓存当前的散列码,只要当参与散列的对象改变时才重新计算,否则调用缓存的
hashCode,这可以从很大程度上提高性能.


默认的实现是将对象内部地址转化为整数作为HashCode,这当然能保证每个对象具有不同
的HasCode,因为不同的对象内部地址肯定不同(废话),但java语言并不能让程序员获取对
象内部地址,所以,让每个对象产生不同的HashCode有着很多可研究的技术.

HashSet class is a concrete implementation of Set interface. It creates a collection that uses a hash table for storage. Hash table stores information by using a mechanism called hashing. In hashing, the informational content of a key is used to determine a unique value, called its hash code. The hash code is then used as an index at which the data associated with the key is stored. The transformation of key into its hash code is performed automatically. You never see the hash code itself. The advantage of hashing is that it allows the execution time of basic operation, such as add(), contains(), remove(), and size() to remain constant even for large sets.

HashSet is not synchronized. If more than one thread wants to access it at the same time then it must be synchronized externally.

This code shows the use of HashSet. This will identify the number of duplicate words in a String. The String is passed as command line arguments.

Add() method of the HashSet add the object into the storage if it is not already present.

import java.util.*;

public class FindDups {

  public static void main(String[] args) {
  
    Set s = new HashSet();

    for(int i=0; i<args.length;i++){
      if(!s.add(args[i]))
        System.out.println("Duplicate detected : " + args[i]);
    }

    System.out.println(s.size() " distinct words detected : " + s );
  }
}

Run the program: C:\> java FindDups i came i came i conquered

Output Screen :

Duplicate detected: i
Duplicate detected: i
4 distinct words detected : [came,saw,conquered,i]

http://en.wikipedia.org/wiki/Hash_code

http://openjava.bokee.com/461061.html

http://www.javaworld.com/community/node/1006
<think>首先,用户查询是关于JavahashCode的介绍和使用。我需要参考提供的引用内容来构建回答。这些引用提供了hashCode的基本概念、作用、实现和一些规则。 关键点从引用中提取: - 引用[1]:展示了Integer和String类的hashCode实现。IntegerhashCode就是其值本身;String的hashCode是通过公式计算(h = 31 * h + val[i])。 - 引用[2]:解释了hashCode的作用:用于查找的快捷性;如果两个对象相等(equals方法),hashCode必须相同;如果重写equals方法,应重写hashCodehashCode相同不一定对象相同。 - 引用[3]:提到hashCode的主要应用是在HashMap等hash表中;列出了各种Java基本类型的hashCode实现细节。 - 引用[4]:描述了hashCode的通用约定:同一对象多次调用返回相同值;如果对象相等,hashCode必须相等;不等对象不一定hashCode不同。ObjecthashCode是native方法。 - 引用[5]:强调hashCode应该稳定,不应该依赖于易变数据,否则在HashMap中可能出现问题。 现在,系统指令要求: - 回答使用中文。 - 结构清晰,帮助用户逐步解决问题。 - 行内数学表达式用$...$格式(如涉及公式)。 - 独立公式用$$...$$格式并单独成段(但这里可能不涉及复杂公式)。 - 生成相关问题。 - 引用的段落末尾添加引用标识,如[^1]。 用户查询是关于Java hashCode的介绍和使用,所以我需要提供: 1. 定义和用途:什么是hashCode?为什么使用它? 2. hashCode的作用:特别是在集合如HashMap、Hashtable中的应用。 3. hashCodeequals的关系。 4. 如何实现hashCode方法:包括通用规则和常见类的实现(如Integer、String)。 5. 注意事项:比如稳定性、一致性。 结构回答: - 开头简要介绍hashCode。 - 逐步解释关键点。 - 使用引用来支持内容,并添加引用标识。 - 结束时生成相关问题。 最后,生成相关问题:基于主题,问一些延伸问题,比如优化、常见错误等。 大纲回答: 1. **引言**:定义hashCode及其在Java中的重要性。 2. **hashCode的作用**:解释为什么需要hashCode,引用[2]和[^4]。 3. **hashCodeequals的关系**:强调规则(对象相等则hashCode相等),引用[2]和[^5]。 4. **实现细节**:描述常见类的hashCode实现,如Integer、String,引用[1]和[^3]。 5. **通用约定**:讨论hashCode的稳定性原则,引用[4]和[^5]。 6. **使用场景**:在HashMap中的应用,引用[3]。 7. **结论**:总结最佳实践。 8. **相关问题**:生成3-4个相关问题。 确保语言自然、中文。 </think>### JavahashCode的介绍和使用 在Java中,`hashCode()` 方法是`Object`类的一部分,它为对象返回一个整数值(哈希),主要用于支持散列存储数据结构(如`HashMap`、`Hashtable`等)的高效查找[^2]。哈希的作用是将对象快速分配到存储桶(bucket)中,减少搜索时间。我会逐步解释其核心概念、使用规则、实现细节和注意事项,帮助你全面理解。 #### 1. **hashCode的作用和重要性** - **高效查找**:在散列表(如`HashMap`)中,对象的哈希用于确定其存储位置(索引)。这样,查找操作的时间复杂度可以接近$O(1)$,而不是线性搜索的$O(n)$。例如,当`HashMap`存储或检索对象时,它先计算哈希定位桶,再使用`equals()`方法比较桶内元素[^2][^3]。 - **与`equals()`的关系**:根据Java规范: - 如果两个对象相等(即`equals()`返回`true`),它们的哈希**必须**相同[^2][^4]。 - 如果两个对象不相等,它们的哈希**不一定**不同(哈希冲突可能发生)[^4]。 - 如果重写了`equals()`方法,**必须**同时重写`hashCode()`,以确保一致性。否则,对象在散列表中可能无法正确查找[^2][^5]。例如,自定义类中如果`equals`基于对象的状态,`hashCode`也应基于相同状态计算。 #### 2. **hashCode的实现规则和通用约定** Java规定了`hashCode()`的通用约定,这些是设计时必须遵守的原则[^4][^5]: - **一致性**:同一对象在多次调用`hashCode()`时应返回相同的值(除非对象状态改变)。这意味着哈希不应依赖于易变数据(如时间戳或随机数),否则在散列表中可能导致对象丢失[^5]。 - 公式化表示:对于对象$O$,多次调用$O.\text{hashCode}()$应得同一整数。 - **相等性关联**:如果$O_1.\text{equals}(O_2)$为真,则$O_1.\text{hashCode}()$必须等于$O_2.\text{hashCode}()$[^2][^4]。 - **不等对象的哈希冲突**:不等对象的哈希可以相同,但应尽量减少冲突以提高性能。理想情况下,哈希应均匀分布。 - 默认实现:`Object`类的`hashCode()`是native方法,通常基于对象内存地址计算[^4]。但对于自定义类,建议重写以实现更合理的分布。 #### 3. **常见类的hashCode实现示例** Java标准库为基本类和常用类提供了优化的`hashCode()`实现。以下是关键例子(基于引用内容): - **`Integer`类**:哈希就是其值本身。例如,`new Integer(20).hashCode()`返回$20$[^1]。 ```java // 代简化表示 public int hashCode() { return value; // value是Integer对象的内部值 } ``` - **`String`类**:哈希通过递归计算每个字符的ASCII值,使用公式$h = 31 \times h + \text{val}[i]$生成。初始$h=0$,然后遍历字符数组。例如,字符串`"123"`的哈希计算过程: - $h = 31 \times 0 + '1' = 0 + 49 = 49$(字符`'1'`的ASCII为49) - $h = 31 \times 49 + '2' = 1519 + 50 = 1569$ - $h = 31 \times 1569 + '3' = 48639 + 51 = 48690$ 结果即为$48690$[^1]。 ```java public int hashCode() { int h = 0; for (char c : value) { // value是字符串字符数组 h = 31 * h + c; } return h; } ``` - **其他基本类型**: - `Long`:哈希是高32位与低32位异或的结果(即`(int)(value ^ (value >>> 32))`)[^3]。 - `Double`和`Float`:将底层二进制位转换为整数后计算哈希[^3]。 - **自定义类实现**:当重写`hashCode()`时,应基于对象的"关键字段"计算。例如,使用`Objects.hash(field1, field2,...)`方法生成复合哈希: ```java public class Person { private String name; private int age; @Override public int hashCode() { return Objects.hash(name, age); // 基于name和age计算哈希 } } ``` #### 4. **使用场景和注意事项** - **主要应用**:`hashCode()`广泛应用于散列集合: - 在`HashMap`中,哈希决定键的存储位置;查找时先哈希定位桶,再`equals()`精确匹配[^3]。 - 在`HashSet`中,哈希用于快速识别元素是否存在(基于`HashMap`实现)[^2]。 - **性能注意事项**: - **哈希冲突**:哈希不均可能导致桶内链表过长(或红黑树退化),降低性能。设计哈希时,应使对象分布均匀。 - **稳定性**:避免哈希依赖可变字段。例如,如果一个对象的哈希基于其位置属性,但位置改变后哈希变化,则对象在`HashMap`中无法被检索[^5]。 - **效率**:计算哈希应快速,避免复杂操作影响集合性能。 - **常见错误**: - 重写`equals()`但不重写`hashCode()`:违反约定,导致对象在散列表中行为异常。 - 使用非一致字段:如基于时间戳计算哈希,会使对象在集合中失效。 #### 5. **最佳实践总结** - 总是同时重写`equals()`和`hashCode()`,并基于相同字段计算。 - 使用质数(如31)在哈希计算中减少冲突(Java的`String`类使用31因其历史性能优势)[^1]。 - 测试哈希分布:通过单元测试验证冲突率。 - 在并发环境中,确保哈希计算线程安全(例如,使用不可变对象)。 通过以上解释,你可以看到`hashCode()`在Java中是散列表高效运作的核心机制。正确实现它,能显著提升应用性能。如需深入代示例或特定用例,可查阅Java官方文档。 ### 相关问题 1. 如何在自定义类中实现高效的hashCode方法以减少冲突? 2. 在HashMap中,hashCode冲突如何处理? 3. 为什么String类的hashCode使用31作为乘数因子? 4. hashCodeequals方法在Java集合框架中的协同工作机制是什么? [^1]: 引用的Integer和String hashCode实现细节。 [^2]: 讨论hashCode在查找中的作用以及与equals的关系。 [^3]: 概述hashCodeJava基本类型中的实现和应用场景。 [^4]: 解释hashCode的通用约定和Object类的默认实现。 [^5]: 强调hashCode一致性原则及其在散列表中的重要性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值