iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >Java 中HashCode作用_动力节点Java学院整理
  • 397
分享到

Java 中HashCode作用_动力节点Java学院整理

javahashcode 2023-05-31 14:05:28 397人浏览 泡泡鱼
摘要

第1 部分 hashCode的作用  Java集合中有两类,一类是List,一类是Set他们之间的区别就在于List集合中的元素师有序的,且可以重复,而Set集合中元素是无序不可重复的。对于List好处理,但是对于Set而言我们要如何来保证

第1 部分 hashCode的作用

  Java集合中有两类,一类是List,一类是Set他们之间的区别就在于List集合中的元素师有序的,且可以重复,而Set集合中元素是无序不可重复的。对于List好处理,但是对于Set而言我们要如何来保证元素不重复呢?通过迭代来equals()是否相等。数据量小还可以接受,当我们的数据量大的时候效率可想而知(当然我们可以利用算法进行优化)。比如我们向HashSet插入1000数据,难道我们真的要迭代1000次,调用1000次equals()方法吗?hashCode提供了解决方案。怎么实现?我们先看hashCode的源码(Object)。

public native int hashCode();

  它是一个本地方法,它的实现与本地机器有关,这里我们暂且认为他返回的是对象存储的物理位置(实际上不是,这里写是便于理解)。当我们向一个集合中添加某个元素,集合会首先调用hashCode方法,这样就可以直接定位它所存储的位置,若该处没有其他元素,则直接保存。若该处已经有元素存在,就调用equals方法来匹配这两个元素是否相同,相同则不存,不同则散列到其他位置。这样处理,当我们存入大量元素时就可以大大减少调用equals()方法的次数,极大地提高了效率。

  所以hashCode在上面扮演的角色为寻域(寻找某个对象在集合中区域位置)。hashCode可以将集合分成若干个区域,每个对象都可以计算出他们的hash码,可以将hash码分组,每个分组对应着某个存储区域,根据一个对象的hash码就可以确定该对象所存储区域,这样就大大减少查询匹配元素的数量,提高了查询效率。 

  如何理解hashCode的作用:

  以java.lang.Object来理解,JVM每new一个Object,它都会将这个Object丢到一个Hash哈希表中去,这样的话,下次做Object的比较或者取这个对象的时候,它会根据对象的hashcode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。具体过程是这样:

new Object(),JVM根据这个对象的Hashcode值,放入到对应的Hash表对应的Key上,如果不同的对象确产生了相同的hash值,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同hashcode的对象放到这个单链表上去,串在一起。

比较两个对象的时候,首先根据他们的hashcode去hash表中找他的对象,当两个对象的hashcode相同,那么就是说他们这两个对象放在Hash表中的同一个key上,那么他们一定在这个key上的链表上。那么此时就只能根据Object的equal方法来比较这个对象是否equal。当两个对象的hashcode不同的话,肯定他们不能equal.

java.lang.Object中对hashCode的约定:

   1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。

   2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
 

  3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

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

第2部分 hashCode对于一个对象的重要性

  一个对象的HashCode就是一个简单的Hash算法的实现,虽然它和那些真正的复杂的Hash算法相比还不能叫真正的算法,它如何实现它,不仅仅是程序员编程水平问题, 而是关系到你的对象在存取是性能的非常重要的关系.有可能,不同的HashCode可能会使你的对象存取产生,成百上千倍的性能差别。先来看一下,在JAVA中两个重要的数据结构:HashMap和Hashtable,虽然它们有很大的区别,如继承关系不同,对value的约束条件(是否允许null)不同,以及线程安全性等有着特定的区别,但从实现原理上来说,它们是一致的。所以,只以Hashtable来说明:

  在java中,存取数据的性能,一般来说当然是首推数组,但是在数据量稍大的容器选择中,Hashtable将有比数据性能更高的查询速度。具体原因看下面的内容,Hashtable在存储数据时,一般先将该对象的HashCode和0x7FFFFFFF做与操作,因为一个对象的HashCode可以为负数,这样操作后可以保证它为一个正整数。然后以Hashtable的长度取模,得到该对象在Hashtable中的索引

int index = (hash & 0x7FFFFFFF) % tab.length;

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

既然一个对象可以根据HashCode直接定位它在Hashtable中的位置,那么为什么Hashtable还要用key来做映射呢?这就是关系Hashtable性能问题的最重要的问题:Hash冲突。

  常见的Hash冲突是不同对象最终产生了相同的索引,而一种非常甚至绝对少见的Hash冲突是,如果一组对象的个数大过了int 范围,而HashCode的长度只能在int范围中,所以肯定要有同一组的元素有相同的HashCode,这样无论如何他们都会有相同的 索引。当然这种极端的情况是极少见的,可以暂不考虑,但是对于同的HashCode经过取模,则会产生相同的索引,或者不同 的对象却具有相同的HashCode,当然具有相同的索引。所以对于索引相同的对象,在该index位置存放了多个值,这些值要想能正确区分,就要依靠key来识别。事实上一个设计各好的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方法方法,都要重新 对方法内的参与散列的对象重新计算一次它们的HashCode的运算,如果一个对象的属性没有改变,仍然要每次都进行计算, 所以如果设置一个标记来缓存当前的散列码,只要当参与散列的对象改变时才重新计算,否则调用缓存的hashCode,这可以 从很大程度上提高性能。默认的实现是将对象内部地址转化为整数作为HashCode,这当然能保证每个对象具有不同的HasCode,因为不同的对象内部地 址肯定不同,但java语言并不能让程序员获取对象内部地址,所以,让每个对象产生不同的HashCode有着很多可研究 的技术。如果从多个属性中采样出能具有平均分布的hashCode的属性,这是一个性能和多样性相矛盾的地方,如果所有属性都参与散 列,当然hashCode的多样性将大大提高,但牺牲了性能,而如果只能少量的属性采样散列,极端情况会产生大量的散列冲突 ,如对"人"的属性中,如果用性别而不是姓名或出生日期,那将只有两个或几个可选的hashcode值,将产生一半以上的散列 冲突.所以如果可能的条件下,专门产生一个序列用来生成HashCode将是一个好的选择(当然产生序列的性能要比所有属性参 与散列的性能高的情况下才行,否则还不如直接用所有属性散列). 如何对HashCode的性能和多样性求得一个平衡,可以参考相关算法设计的书,其实并不一定要求非常的优秀,只要能尽最大 可能减少散列值的聚集。重要的是我们应该记得HashCode对于我们的程序性能有着重要的影响,在程序设计时应该时时加以注 意.

请记住:如果你想有效的使用HashMap,你就必须重写在其的HashCode()。

还有两条重写HashCode()的原则:

不必对每个不同的对象都产生一个唯一的hashcode,只要你的HashCode方法使get()能够得到put()放进去的内容就可以了。即“不为一原则”。

生成hashcode的算法尽量使hashcode的值分散一些, 不要很多hashcode都集中在一个范围内,这样有利 于提高HashMap的性能。即“分散原则”。

至于第二条原则的具体原因,有兴趣者可以参考Bruce Eckel的《Thinking in Java》,在那里有对HashMap内部实现原理的介绍,这里就不赘述了。 

第3部分 hashCode与equals

在Java中hashCode的实现总是伴随着equals,他们是紧密配合的,你要是自己设计了其中一个,就要设计另外一个。当然在多数情况下,这两个方法是不用我们考虑的,直接使用默认方法就可以帮助我们解决很多问题。但是在有些情况,我们必须要自己动手来实现它,才能确保程序更好的运作。

      对于equals,我们必须遵循如下规则:

      对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。

      反射性:x.equals(x)必须返回是“true”。

      类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。

      一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。

      任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。

      对于hashCode,我们应该遵循如下规则:

      1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。   

   2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。

      3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

      至于两者之间的关联关系,我们只需要记住如下即可:

      如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。

      如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。

      理清了上面的关系我们就知道他们两者是如何配合起来工作的。先看下图: 

Java 中HashCode作用_动力节点Java学院整理

      整个处理流程是:

      1、判断两个对象的hashcode是否相等,若不等,则认为两个对象不等,完毕,若相等,则比较equals。

      2、若两个对象的equals不等,则可以认为两个对象不等,否则认为他们相等。

实例: 

 package com.weakHashMap;  public class Person {  private int age;  private int sex; // 0:男,1:女  private String name;  private final int PRIME = 37;  Person(int age ,int sex ,String name){   this.age = age;   this.sex = sex;   this.name = name;  }  public int getAge() {   return age;  }  public void setAge(int age) {   this.age = age;  }  public int getSex() {   return sex;  }  public void setSex(int sex) {   this.sex = sex;  }  public String getName() {   return name;  }  public void setName(String name) {   this.name = name;  }  @Override  public int hashCode() {   System.out.println("调用hashCode方法...........");   int hashResult = 1;   hashResult = (hashResult + Integer.valueOf(age).hashCode() + Integer.valueOf(sex).hashCode()) * PRIME;   hashResult = PRIME * hashResult + ((name == null) ? 0 : name.hashCode());    System.out.println("name:"+name +" hashCode:" + hashResult);   return hashResult;  }    public boolean equals(Object obj) {   System.out.println("调用equals方法...........");   if(obj == null){    return false;   }   if(obj.getClass() != this.getClass()){    return false;   }   if(this == obj){    return true;   }   Person person = (Person) obj;   if(getAge() != person.getAge() || getSex()!= person.getSex()){    return false;   }   if(getName() != null){    if(!getName().equals(person.getName())){     return false;    }   }   else if(person != null){    return false;   }   return true;  }  }  package com.weakHashMap; import java.util.HashSet; import java.util.Set;  public class test extends Person{    test(int age, int sex, String name) {   super(age, sex, name);   // TODO Auto-generated constructor stub  }  public static void main(String[] args){   Set<Person> set = new HashSet<Person>();   Person p1 = new Person(11, 1, "张三");   Person p2 = new Person(12, 1, "李四");  Person p3 = new Person(11, 1, "张三");   Person p4 = new Person(11, 1, "李四");   //只验证p1、p3   System.out.println("p1 == p3? :" + (p1 == p3));  System.out.println("p1.equals(p3)?:"+p1.equals(p3));   System.out.println("-----------------------分割线--------------------------");  set.add(p1);  set.add(p2);  set.add(p3);   set.add(p4);  System.out.println("set.size()="+set.size()); } }

--结束END--

本文标题: Java 中HashCode作用_动力节点Java学院整理

本文链接: https://www.lsjlt.com/news/226422.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java 中HashCode作用_动力节点Java学院整理
    第1 部分 hashCode的作用  Java集合中有两类,一类是List,一类是Set他们之间的区别就在于List集合中的元素师有序的,且可以重复,而Set集合中元素是无序不可重复的。对于List好处理,但是对于Set而言我们要如何来保证...
    99+
    2023-05-31
    java hashcode
  • 探索Java中的equals()和hashCode()方法_动力节点Java学院整理
    equals()和hashCode()区别?  equals():反映的是对象或变量具体的值,即两个对象里面包含的值--可能是对象的引用,也可能是值类型的值。  hashCode():...
    99+
    2023-05-31
    java equals hashcode
  • Java接口的作用_动力节点Java学院整理
    1. 接口是一种规范很好,你已经知道接口是一种规范了!下面这张图是我们生活中遇到的接口:电源插座接口。2. 为什么需要规范呢?因为有了接口规范:• 任何电器只有有符合规范的插头,就可以获得电力•&...
    99+
    2023-05-31
    java 接口
  • Java死锁_动力节点Java学院整理
    死锁是两个甚至多个线程被永久阻塞时的一种运行局面,这种局面的生成伴随着至少两个线程和两个或者多个资源。在这里我已写好一个简单的程序,它将会引起死锁方案然后我们就会明白如何分析它。Java死锁范例ThreadDeadlock.javapack...
    99+
    2023-05-31
    java 死锁 ava
  • Java多态(动力节点Java学院整理)
    什么是多态 面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而准备的。这是我们最后一个概念,也是最重要的知识点。 多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象...
    99+
    2023-05-31
    java 多态 ava
  • HashSet工作原理_动力节点Java学院整理
    对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,查看 HashSet 的源代码,可以看到如下代码:public class Has...
    99+
    2023-05-31
    java hashset hs
  • Java中StringBuffer和StringBuilder_动力节点Java学院整理
    下面先给大家介绍下String、StringBuffer、StringBuilder区别,具体详情如下所示:StringBuffer、StringBuilder和String一样,也用来代表字符串。String类是不可变类,任何对Strin...
    99+
    2023-05-31
    java stringbuffer stringbuilder
  • Java二进制操作(动力节点Java学院整理)
    移位位运算中大多数操作都是向左移位和向右移位。在Java中,这对应着<<和>>这两个操作符,示例如下:1 << 1 == 2 1 << 3 == 80xFFFFFFF0 >&g...
    99+
    2023-05-31
    java 二进制
  • Java中Random简介_动力节点Java学院整理
    Java中存在着两种Random函数:一、java.lang.Math.Random;  调用这个Math.Random()函数能够返回带正号的double值,该值大于等于0.0且小于1.0,即取值范围是[0.0,1.0)的左闭右开区间,返...
    99+
    2023-05-31
    java random ava
  • Java链接redis_动力节点Java学院整理
    要在Java程序中使用使用操作Redis,需要确保有Redis的Java驱动程序和Java设置在机器上。现在,让我们来看看如何设置Redis的Java驱动程序。 需要下载jedis.jar。请一定要下载它的最新版本。 需要包括jedis...
    99+
    2023-05-31
    java redis 链接
  • Java Map简介_动力节点Java学院整理
    Map简介将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。此接口取代 Dictionary 类,后者完全是一个抽象类,而不是一个接口。 Map 接口提供三种collection 视图,允许以键集、值集或键-值映射关...
    99+
    2023-05-31
    java map ava
  • Serializable接口的作用_动力节点Java学院整理
    实现java.io.Serializable 接口的类是可序列化的。没有实现此接口的类将不能使它们的任一状态被序列化或逆序列化。序列化类的所有子类本身都是可序列化的。这个序列化接口没有任何方法和域,仅用于标识 序列化的语意。允许非序列化类的...
    99+
    2023-05-31
    java serializable 接口
  • Java 线程池_动力节点Java学院整理
    线程池系统启动一个新线程的成本是比较高的,因为它涉及到与操作系统的交互。在这种情况下,使用线程池可以很好的提供性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。与数据库连接池类似的是,线程池在系统启动时即创建大量空...
    99+
    2023-05-31
    java 线程池 ava
  • Java内部类_动力节点Java学院整理
    内部类是指在一个外部类的内部再定义一个类。类名不需要和文件夹相同。内部类可以是静态static的,也可用public,default,protected和private修饰。(而外部顶级类即类名和文件名相同的只能使用public和defau...
    99+
    2023-05-31
    java 内部类 ava
  • Java Set简介_动力节点Java学院整理
    1. 概述           Java 中的Set和正好和数学上直观的集(set)的概念是相同的。Set最大的特性就是不允许在其中存放的元素是重复的...
    99+
    2023-05-31
    java set ava
  • Java FileDescriptor总结_动力节点Java学院整理
    FileDescriptor 介绍FileDescriptor 是“文件描述符”。FileDescriptor 可以被用来表示开放文件、开放套接字等。以FileDescriptor表示文件来说:当FileDescriptor表示某文件时,我...
    99+
    2023-05-31
    java filedescriptor ava
  • JDBC简介_动力节点Java学院整理
    前言:什么是JDBC维基百科的简介:  Java 数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。J...
    99+
    2023-05-31
    jdbc 简介 ava
  • Java中JDom解析XML_动力节点Java学院整理
    一、前言    JDOM是Breet Mclaughlin和Jason Hunter两大Java高手的创作成果,2000年初,JDOM作为一个开放源代码项目正式开始研发。JDOM是一种解析XML的Ja...
    99+
    2023-05-31
    jdom 解析 xml
  • Java中的PrintWriter 介绍_动力节点Java学院整理
    PrintWriter 介绍PrintWriter 是字符类型的打印输出流,它继承于Writer。PrintStream 用于向文本输出流打印对象的格式化表示形式。它实现在 PrintStream 中的所有 print 方法。它不包含用于写...
    99+
    2023-05-31
    java printwriter ava
  • Java中的关键字_动力节点Java学院整理
    Java中常用关键字:与数据类型相关(10)与流程控制相关(13)if: 表示条件判断,一般用法if(关系表达式),后跟else或{……}else: 条件转折,如if (关系表达式){语句块1}else{语句块2},如果...
    99+
    2023-05-31
    java 关键字 ava
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作