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 求余数直接找到存放的位置了。 java
可是若是两个类有相同的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()来比较一下他们做用就能够拉,关键要本身动手看看比较的结果你就能够记得很清楚啦
若是还不是很明确能够再看另外一个例子: shell
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;
}
public native int hashCode();
从上面咱们能够看到是否极可能Object.hashCode就是表明内存地址。下面咱们来证实hashcode是否是真的就是Object的内存地址呢?实际上,hashcode根本不能表明object的内存地址。
-----------------------------------------
Object.hashCode不能够表明内存地址
----------------------------------------
/**
* 此方法的做用是证实 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());
}
}
==============================
看HashTable的源代码很是有用:
==============================
============================================================
有效和正肯定义hashCode()和equals():
============================================================
级别:入门级
每一个Java对象都有hashCode()和 equals()方法。许多类忽略(Override)这些方法的缺省实施,以在对象实例之间提供更深层次的语义可比性。在Java理念和实践这一部分, Java开发人员Brian Goetz向您介绍在建立Java类以有效和准肯定义hashCode()和equals()时应遵循的规则和指南。您能够在讨论论坛与做者和其它读者一 同探讨您对本文的见解。(您还能够点击本文顶部或底部的讨论进入论坛。)
虽然Java语言不直接支持关联数组 -- 可使用任何对象做为一个索引的数组 -- 但在根Object类中使用hashCode()方法明确表示指望普遍使用HashMap(及其前辈Hashtable)。理想状况下基于散列的容器提供 有效插入和有效检索;直接在对象模式中支持散列能够促进基于散列的容器的开发和使用。
定义对象的相等性
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有着不少可研究的技术.