`

为什么HashCode对于对象是如此的重要?

    博客分类:
  • Java
阅读更多

  一个对象的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对于我们的程序性能有着生要的影响,在程序设计时应该时时加以注意。 

分享到:
评论

相关推荐

    java中为何重写equals时必须重写hashCode方法详解

    现在,让我们深入探讨为什么重写 `equals()` 时要重写 `hashCode()`: 1. **一致性**:一旦对象被创建并赋予了特定的值,其 `equals()` 和 `hashCode()` 方法的结果就应该保持不变,即使在程序的不同执行期间也是...

    HashSet,TreeSet和LinkedHashSet的区别1

    在Java编程语言中,集合框架是处理数据的...在使用这些集合类时,确保正确地实现了equals()、hashCode()(对于HashSet和LinkedHashSet)以及Comparable或Comparator(对于TreeSet)方法,是保证集合行为正确性的关键。

    浅谈为什么Java里面String类是不可变的

    那么,为什么 Java 语言的设计者要把 String 类型设计成不可变对象呢?下面,我们将深入探讨字符串不可变性的原因和优点。 不可变对象的定义 不可变对象指的是对象创建之后,对象的内部状态以及对象的内存指针地址...

    JAVA面试题和答案.docx

    如此,即使两个`Cat`对象具有相同的属性值,`equals()`也会返回false,因为它们是不同的对象实例。为了使`equals()`比较对象的内容,需要在`Cat`类中添加自定义的`equals()`实现。 通过了解这些基础知识,面试者...

    IBM、SUN等公司的Java面试题

    因此,为了保证`Set`正确工作,通常还需要覆盖`hashCode()`方法,以确保当两个对象通过`equals()`方法比较相等时,它们的`hashCode()`值也相等。 #### 十六、常见的RuntimeException - `RuntimeException`是Java中...

    Java中equals方法隐藏的陷阱

    虽然这看起来更直观,但实际上会导致类型转换异常,当尝试比较不同类型但继承自同一父类的对象时尤其如此。 - **返回类型错误**:另一个常见的错误是将`equals`方法的返回类型设置为`void`或其他非布尔类型的值。这...

    深入解析Java编程中方法的参数传递

    这就是为什么人们有时会说Java的对象参数“看起来”像按引用传递,但实际上它们仍然是按值传递的。 理解这一点对于编写可预测的Java代码至关重要,特别是当你涉及到多线程、并发或者需要控制对象的可见性变化时。在...

    kotlin-coding-puzzle,kotlin编码难题及解决方案.zip

    5. **数据类与密封类**:数据类简化了创建具有equals()、hashCode()和toString()方法的对象,而密封类用于表示有限的枚举类,常用于定义有限状态机或异常处理。了解它们如何协同工作可以帮助优化代码结构。 6. **...

    Google_Contests

    这些比赛通常涉及C++等编程语言,因此对于C++开发者来说,熟悉相关知识并能够高效运用是至关重要的。 首先,让我们深入了解Google KickStart。这是一个在线编程竞赛,针对初级到高级的程序员,每年举行多次。比赛...

    Jakarta Commons笔记

    ### Jakarta Commons概述与重要性 在Java开发领域中,开源框架的重要性不容小觑。它们不仅提供了现成的解决方案,还能显著提升开发效率和代码质量。其中,Jakarta Commons作为一个广泛使用的工具集,对于中型乃至...

    写Java程序的三十个基本规则

    这些方法对于调试和对象比较非常关键,尤其是在实现`Cloneable`接口或者`Serializable`接口时更是如此。 #### 规则三:main方法的重要性 每个Java应用程序至少包含一个入口点,即`main`方法。即使项目中不直接调用...

    java C++ 文档

    然而,即便如此,新手和经验丰富的开发者都可能在编写代码时犯一些低级错误,这些错误可能导致程序运行异常、性能下降或难以维护。Java新十大低级错误包括: 1. 忘记初始化变量:未初始化的变量可能导致不可预测的...

Global site tag (gtag.js) - Google Analytics