`
rommal7090
  • 浏览: 107542 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论
阅读更多
 一个对象的HashCode就是一个简单的Hash算法的实现,虽然它和那些真正的复杂的Hash算法相比还不能叫真正的算法,它如何实现它,不仅仅是程序员的编程水平问题,而是关系到你的对象在存取是性能的非常重要的关系.有可能,不同的HashCode可能会使你的对象存取产生,成百上千倍的性能差别。

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

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

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

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

  虽然不同对象有不同的hashcode,但不同的hashCode经过与长度的取余,就很可能产生相同的index。

  极端情况下会有大量的对象产生一个相同的索引.这就是关系Hashtable性能问题的最重要的问题:

  Hash冲突。

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

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

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

  如果从多个属性中采样出能具有平均分布的hashCode的属性,这是一个性能和多样性相矛盾的地方,如果所有属性都参与散列,当然hashCode的多样性将大大提高,但牺牲了性能,而如果只能少量的属性采样散列,极端情况会产生大量的散列冲突,如对"人"的属性中,如果用性别而不是姓名或出生日期,那将只有两个或几个可选的hashcode值,将产生一半以上的散列冲突.所以如果可能的条件下,专门产生一个序列用来生成HashCode将是一个好的选择(当然产生序列的性能要比所有属性参与散列的性能高的情况下才行,否则还不如直接用所有属性散列)。

  如何对HashCode的性能和多样性求得一个平衡,可以参考相关算法设计的书,其实并不一定要求非常的优秀,只要能尽最大可能减少散列值的聚集.重要的是我们应该记得HashCode对于我们的程序性能有着生要的影响,在程序设计时应该时时加以注意。

示例:

2、关于Hashtable,判断key是否相同的条件是:hashCode()相同 && 满足equals()。这两个方法都是可以重构的,所以Hashtable没有用==,给了程序员实现自己的Hashtable的好的途径,的确Sun想得很严密。

例子1:
class a{
private int b;
public a(int c){//构造函数
b=c;
}
public String eqals(Object o){
if(this==o) return true;
if(o instanceof a){
return a.b==(a)o.b;
}
return false;
}
public static void main(String[] args){
Map p=new HashMap();
p.put(new a(1));
p.get(new a(1))
/*这里返回为null,因为map是根据equals()和hashCode()来判断对象是否相等,所以在类里覆写了equals(),就一定要覆写hashCode()。关于Hashtable,判断key是否相同的条件是:hashCode()相同 && 满足equals(),而一个类如果没有覆写equals()方法,那么这个类的equals方法比较的是对象的内存地址。如果没有覆写HashCode,那么该类的hashCode是通过对象的内存地址转换而来.
*/
}
}
例子二:
/**
* 类:TestStudent
*/
public class TestStudent {
private int age;

public TestStudent(int age) {
this.age = age;
}

public TestStudent() {
}

public boolean equals(Object o) {
// if (this == o) return true;
// if (o == null || getClass() != o.getClass()) return false;
//
// final TestStudent that = (TestStudent) o;
//
// if (age != that.age) return false;

return true;
}

public int hashCode() {
return age;
}
}
/**
* 类:TestTeacher
*/
public class TestTeacher {
private int age;

public TestTeacher(int age) {
this.age = age;
}

public TestTeacher() {
}

public boolean equals(Object o) {
// if (this == o) return true;
// if (o == null || getClass() != o.getClass()) return false;
//
// final TestTeacher that = (TestTeacher) o;
//
// if (age != that.age) return false;
//
// return true;
return true;
}

public int hashCode() {
return age;
}
}

public class TestHashtable2 {
public static void main(String[] args){
Hashtable table=new Hashtable();
table.put(new TestTeacher(1),"aaa");
System.out.print("=========="+table.get(new TestStudent(1)));
}
}
输出结果是:==========aaa
从上面这个例子就可以看出来Hashtable中key和对象所属的class无关,只和equals方法和hashCode方法的有关。当然正常情况下我们覆写equals方法的时候下面两句话是必不可少的。
if (this == o) return true;//保证了统一对对象的绝对一致性。
if (o == null || getClass() != o.getClass()) return false;
final TestStudent that = (TestStudent) o;//保证了只有同一个类的不同对象进行equals,虽然上面的例子可以进行equals对比不同的对象,但是实际情况中不同对象间的equals进行对比意义不大。
if (age != that.age){
return false;
}

3、在程序执行期间,同一个对象调用hashCode()必须返回同一个值(同一个应用执行期)
4、如果两个对象equals,那么他们的hashCode()必须相等。(个人感觉这个要求也是为了和java的结合框架协同工作,因为我们自己定义的类完全可以覆写equals方法,但是不覆写hashCode,尽管我们可以这样做,但是这样做是不合理的,他会为我们的程序埋下错误隐患。虽然java没有从在编译器对上面的说法进行控制,但是上面的说法是我们在设计类的时候应该遵循的规则)。
5、如果两个对象equals不相等,那么他们的hashCode()不必产生不同的结果(上面的例2就可以说明问题),程序员应该注意到,对不同的对象产生不同的hashCode(),有可能提升 hash table(哈希表)的效率
7、Map.put(key,value)时根据key.hashCode生成一个内部hash值,根据这个hash值将对象存放在一个table中。Map.get(key)会比较key.hashCode和equals方法,当且仅当这两者相等时,才能正确定位到table;java中Set是通过Map实现的,所以Map和Set的所有实现类都要注意这一点.

8、
StringBuffer buffer = new StringBuffer();
buffer.append("some");
String a = buffer.toString();
String b = "some";

现在有三个式子,结果在后面用注释的形式标出:

a.equals(b);//true
a==b;//false
a.hashCode()==b.hashCode();//true

说明:"ab"=="ab"是因为编译的时候就把这两个指向同一个常量了,属于编译优化的一部分。但是动态生成的字符串的地址就不一样了。比如String a = buffer.toString(), 但是buffer.append("some")和String b = "some"里两个"some"常量应当是指向同一个地址。

9、如果想判断是否两个对象引用指向同一个实体,唯一的正确途径是使用==,因为只有它不会被重构,要慎用hashCode

分享到:
评论

相关推荐

    关于HashCode码的重复问题 两种验证实例

    1,如果两个对象相同,那么它们的hashCode值一定要相同; 2,如果两个对象的hashCode相同,它们并不一定相同 上面说的对象相同指的是用eqauls方法比较。 3,HashCode码不唯一

    计算机后端-Java-Java核心基础-第24章 集合01 23. 关于hashCode()和equals()的重写.avi

    计算机后端-Java-Java核心基础-第24章 集合01 23. 关于hashCode()和equals()的重写.avi

    关于hashCode()和equals()的本质区别和联系

    hashCode() 和 equals() 的本质区别和联系 Java 中的每个对象都有 hashCode() 和 equals() 方法,这两个方法的正确实现对于 Java 开发人员来说是非常重要的。本文将详细介绍 hashCode() 和 equals() 的本质区别和...

    hashcode和equals方法

    equals()和hashcode()这两个方法都是从object类中继承过来的。当String 、Math、还有Integer、Double。。。。等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法.

    HashCode的用法详解

    hashCode 的用法详解 hashCode 是一种用于查找和排序的机制,在数据结构中 plays a crucial role。下面我们将对 hashCode 的用法进行详细的解释。 hashCode 的作用 hashCode 的主要作用是用于查找和排序。在查找...

    深入 HashCode 方法~

    ### 深入理解 HashCode 方法 #### 一、HashCode 的基本概念与作用 在 Java 编程语言中,`HashCode` 是一个非常重要且基础的概念。简单来说,`HashCode` 是一个整数值,用于快速定位对象的位置。在 Java 中,每一个...

    Java中hashCode的作用

    以下是关于HashCode的官方文档定义:  hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。  hashCode 的常规协定是:  在Java应用程序执行期间...

    java中Hashcode的作用.docx

    "Java中Hashcode的作用" Hashcode是Java编程语言中一个非常重要的概念,它在equals方法中扮演着关键角色。在Java中,每个对象都具有一个独特的Hashcode,它可以用来标识对象的身份。但是Hashcode是什么?它是如何...

    重写equals和hashcode方法_equals_重写equals和hashcode方法_

    在Java编程语言中,`equals()` 和 `hashCode()` 方法是Object类中的两个核心方法,所有类都默认继承自Object类。这两个方法在处理对象比较和集合操作时起着至关重要的作用。当我们创建自定义类并需要对对象进行精确...

    hashcode的作用

    ### HashCode的作用详解 #### 一、HashCode的基本概念 在Java中,`hashCode()` 方法是 `Object` 类的一个重要成员方法,它返回一个整数,这个整数通常用来表示对象的哈希值。哈希值在Java集合框架中扮演着至关重要...

    equals与hashCode方法讲解

    equals 与 hashCode 方法讲解 equals 方法和 hashCode 方法是 Java 语言中两个重要的方法,它们都是在 Object 类中定义的。equals 方法用于比较两个对象是否相等,而 hashCode 方法用于返回对象的哈希码。 在 Java...

    深入HashCode

    《深入HashCode》 在计算机科学领域,特别是在Java和许多其他面向对象编程语言中,`hashCode()`方法是一个至关重要的概念。这个方法是每个对象都具备的,它返回一个整数值,通常用于快速比较对象或者在哈希表(如...

    hashCode的作用

    ### hashCode的作用 在Java编程语言中,`hashCode`方法是一个重要的概念,主要用于对象的查找与存储,尤其是在集合框架中有着广泛的应用。为了更好地理解`hashCode`的作用及其在实际开发中的重要性,我们可以从以下...

    Java中hashCode和equals方法的正确使用

    在这篇文章中,我将告诉大家我对hashCode和equals方法的理解。我将讨论他们的默认实现,以及如何正确的重写他们。我也将使用Apache Commons提供的工具包做一个实现。  hashCode()和equals()定义在Object类中,这...

    HashCode相同equals不同的2位字符集合算法

    在Java编程语言中,`hashCode()` 和 `equals()` 是两个非常重要的方法,它们主要用于对象的比较和哈希表(如HashMap)的操作。标题提到的"HashCode相同equals不同的2位字符集合算法"涉及到的是一个特定场景:两个...

    Java理论与实践:hashCode()和equals()方法

    本文介绍了Java语言不直接支持关联数组,可以使用任何对象作为一个索引的数组,但在根Object类中使用 hashCode()方法明确表示期望广泛使用HashMap。理想情况下基于散列的容器提供有效插入和有效检索;直接在对象模式...

    java中hashcode()和equals()的详解

    在Java编程语言中,`hashCode()`...以上就是关于Java中`hashCode()`和`equals()`的详解。这两个方法在Java编程中起着至关重要的作用,尤其是在处理集合类和数据结构时。了解并正确使用它们能够确保程序的正确性和效率。

    复写hashCode()方法,和equasl()方法

    ### 复写`hashCode()`方法与`equals()`方法 在Java编程中,`hashCode()`方法与`equals()`方法是对象比较中的两个非常重要的方法。它们主要用于判断对象是否相等以及对象的散列值,这对于集合类(如`HashSet`)来说...

Global site tag (gtag.js) - Google Analytics