关于Java中的equals()方法与hashCode()方法
2014-02-08 17:38
246 查看
散列函数,散列算法,哈希函数。
是一种从任何一种数据中创建小的数字“指纹”的方法。
散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。
好的散列函数在输入域中很少出现散列冲突。
=================================================================================
所有散列函数都有如下一个基本特性:
1:如果a=b,则h(a) = h(b)。
2:如果a!=b,则h(a)与h(b)可能得到相同的散列值。
=================================================================================
这里简单说一下常见的散列算法:
(1) MD4
MD4(RFC 1320)是 MIT 的 Ronald L. Rivest 在 1990 年设计的,MD 是 Message Digest 的缩写。
它适用在32位字长的处理器上用高速软件实现--它是基于 32 位操作数的位操作来实现的。
(2) MD5
MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本。它对输入仍以512位分组,
其输出是4个32位字的级联,与 MD4 相同。MD5比MD4来得复杂,并且速度较之要慢一点,
但更安全,在抗分析和抗差分方面表现更好
(3) SHA-1 及其他
SHA1是由NIST NSA设计为同DSA一起使用的,它对长度小于264的输入,产生长度为160bit的散列值,
因此抗穷举(brute-force)性更好。SHA-1 设计时基于和MD4相同原理,并且模仿了该算法。
=================================================================================
散列算法的日常用处:
(1) 文件校验
我们比较熟悉的校验算法有奇偶校验和CRC校验,这2种校验并没有抗数据篡改的能力,
它们一定程度上能检测并纠正数据传输中的信道误码,但却不能防止对数据的恶意破坏。
MD5 Hash算法的"数字指纹"特性,使它成为目前应用最广泛的一种文件完整性校验和(Checksum)算法,
不少Unix系统有提供计算md5 checksum的命令。
(2) 数字签名
Hash 算法也是现代密码体系中的一个重要组成部分。由于非对称算法的运算速度较慢,
所以在数字签名协议中,单向散列函数扮演了一个重要的角色。 对 Hash 值,
又称"数字摘要"进行数字签名,在统计上可以认为与对文件本身进行数字签名是等效的。
而且这样的协议还有其他的优点。
(3) 鉴权协议
如下的鉴权协议又被称作挑战--认证模式:在传输信道是可被侦听,
但不可被篡改的情况下,这是一种简单而安全的方法。
=================================================================================
Java中的散列
1.java.lang.Object的约定如下:
(1)同一对象上多次调用hashCode()方法,总是返回相同的整型值。
(2)如果a.equals(b),则一定有a.hashCode() 一定等于 b.hashCode()。
(3)如果!a.equals(b),则a.hashCode() 不一定等于 b.hashCode()。
[b]此时如果a.hashCode() 总是不等于 b.hashCode(),会提高hashtables的性能。[/b]
==================================
Object中的默认实现
/**
*As much as is reasonably practical,
*the hashCode method defined by class Object does return distinct integers for distinct objects.
*(This is typically implemented by converting the internal address of the object into an integer,
*but this implementation technique is not required by the Java TM programming language.)
*/
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
==================================
String中的默认实现
//equals()方法与hashCode()没有任何关系
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
//String重写了hashCode()方法
public int hashCode() {
int h = hash;
int len = count;
if (h == 0 && len > 0) {
int off = offset;
char val[] = value;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
==================================
Integer中的默认实现
//Integer的equals()默认就是比较hashCode(),而hashCod()默认就是Integer.intValue()
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
//Integer的hashCode默认就是Integer.intValue()
public int hashCode() {
return value;
}
public Integer(int value) {
this.value = value;
}
==================================
HashMap.contains()的实现如下
/**
* Applies a supplemental hash function to a given hashCode,
* which defends against poor quality hash functions.
*/
//这是一个增强型的hash
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
final Entry<K,V> getEntry(Object key) {
//先调用key.hashCod(),再进行了一次增强型hash
int hash = (key == null) ? 0 : hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
//先比较hash值,必须相等,再调用两个对象的equals()方法,也必须相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
=================================================================================
结论:
(1):可以这样理解哈希(或叫散列),对原始文本进行压缩,产生一个简介(即摘要),并有以下规则:
不同的文本,压缩后,大部分都能得到不同的摘要,但也有少数产生相同的摘要,即产生了碰撞。
相同的文本,压缩后,一定得到相同的摘要。
(2):equals()我们理解成:比较文本是否一样,hashCode()则理解成:比较摘要是否一样。
(3):所以
equals()相同,则hashCode()一定相同。
equals()不相同,hashCode()大部分也不一样,但也有少数产生相同的hashCode(),即产生了碰撞。
(4):所以我们可以这样理解HashMap.get(key)方法
hashCode()是对象的摘要,只不过这个摘要被摘要生成算法限制在[0, table.length - 1]这个闭区间中。
根据key的生成摘要(即数组中的下标),对这个下标上的链表进行循环比较
在摘要相同的前提下(实际上这个链表中的所有元素的摘要都相同,这也正是碰撞的处理方法),
且equals()方法相同(即比较实际上真正关心的内容),则返回正确值。
(5):所以,
如果,equals()相同,而hashCode()不相同,
那么put(key, value)时,原本已存在的key,会被错误的计算出这个key不存在,
所以又在其它位置上存储了一个业务逻辑一样的对象,两个对象之间唯一的区别就是,hash值不一样。
但是get(key)时还是正确的,即取出的还是业务逻辑一样的对象的值。
测试代码如下:
是一种从任何一种数据中创建小的数字“指纹”的方法。
散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。
好的散列函数在输入域中很少出现散列冲突。
=================================================================================
所有散列函数都有如下一个基本特性:
1:如果a=b,则h(a) = h(b)。
2:如果a!=b,则h(a)与h(b)可能得到相同的散列值。
=================================================================================
这里简单说一下常见的散列算法:
(1) MD4
MD4(RFC 1320)是 MIT 的 Ronald L. Rivest 在 1990 年设计的,MD 是 Message Digest 的缩写。
它适用在32位字长的处理器上用高速软件实现--它是基于 32 位操作数的位操作来实现的。
(2) MD5
MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本。它对输入仍以512位分组,
其输出是4个32位字的级联,与 MD4 相同。MD5比MD4来得复杂,并且速度较之要慢一点,
但更安全,在抗分析和抗差分方面表现更好
(3) SHA-1 及其他
SHA1是由NIST NSA设计为同DSA一起使用的,它对长度小于264的输入,产生长度为160bit的散列值,
因此抗穷举(brute-force)性更好。SHA-1 设计时基于和MD4相同原理,并且模仿了该算法。
=================================================================================
散列算法的日常用处:
(1) 文件校验
我们比较熟悉的校验算法有奇偶校验和CRC校验,这2种校验并没有抗数据篡改的能力,
它们一定程度上能检测并纠正数据传输中的信道误码,但却不能防止对数据的恶意破坏。
MD5 Hash算法的"数字指纹"特性,使它成为目前应用最广泛的一种文件完整性校验和(Checksum)算法,
不少Unix系统有提供计算md5 checksum的命令。
(2) 数字签名
Hash 算法也是现代密码体系中的一个重要组成部分。由于非对称算法的运算速度较慢,
所以在数字签名协议中,单向散列函数扮演了一个重要的角色。 对 Hash 值,
又称"数字摘要"进行数字签名,在统计上可以认为与对文件本身进行数字签名是等效的。
而且这样的协议还有其他的优点。
(3) 鉴权协议
如下的鉴权协议又被称作挑战--认证模式:在传输信道是可被侦听,
但不可被篡改的情况下,这是一种简单而安全的方法。
=================================================================================
Java中的散列
1.java.lang.Object的约定如下:
(1)同一对象上多次调用hashCode()方法,总是返回相同的整型值。
(2)如果a.equals(b),则一定有a.hashCode() 一定等于 b.hashCode()。
(3)如果!a.equals(b),则a.hashCode() 不一定等于 b.hashCode()。
[b]此时如果a.hashCode() 总是不等于 b.hashCode(),会提高hashtables的性能。[/b]
==================================
Object中的默认实现
/**
*As much as is reasonably practical,
*the hashCode method defined by class Object does return distinct integers for distinct objects.
*(This is typically implemented by converting the internal address of the object into an integer,
*but this implementation technique is not required by the Java TM programming language.)
*/
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
==================================
String中的默认实现
//equals()方法与hashCode()没有任何关系
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
//String重写了hashCode()方法
public int hashCode() {
int h = hash;
int len = count;
if (h == 0 && len > 0) {
int off = offset;
char val[] = value;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
==================================
Integer中的默认实现
//Integer的equals()默认就是比较hashCode(),而hashCod()默认就是Integer.intValue()
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
//Integer的hashCode默认就是Integer.intValue()
public int hashCode() {
return value;
}
public Integer(int value) {
this.value = value;
}
==================================
HashMap.contains()的实现如下
/**
* Applies a supplemental hash function to a given hashCode,
* which defends against poor quality hash functions.
*/
//这是一个增强型的hash
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
final Entry<K,V> getEntry(Object key) {
//先调用key.hashCod(),再进行了一次增强型hash
int hash = (key == null) ? 0 : hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
//先比较hash值,必须相等,再调用两个对象的equals()方法,也必须相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
=================================================================================
结论:
(1):可以这样理解哈希(或叫散列),对原始文本进行压缩,产生一个简介(即摘要),并有以下规则:
不同的文本,压缩后,大部分都能得到不同的摘要,但也有少数产生相同的摘要,即产生了碰撞。
相同的文本,压缩后,一定得到相同的摘要。
(2):equals()我们理解成:比较文本是否一样,hashCode()则理解成:比较摘要是否一样。
(3):所以
equals()相同,则hashCode()一定相同。
equals()不相同,hashCode()大部分也不一样,但也有少数产生相同的hashCode(),即产生了碰撞。
(4):所以我们可以这样理解HashMap.get(key)方法
hashCode()是对象的摘要,只不过这个摘要被摘要生成算法限制在[0, table.length - 1]这个闭区间中。
根据key的生成摘要(即数组中的下标),对这个下标上的链表进行循环比较
在摘要相同的前提下(实际上这个链表中的所有元素的摘要都相同,这也正是碰撞的处理方法),
且equals()方法相同(即比较实际上真正关心的内容),则返回正确值。
(5):所以,
如果,equals()相同,而hashCode()不相同,
那么put(key, value)时,原本已存在的key,会被错误的计算出这个key不存在,
所以又在其它位置上存储了一个业务逻辑一样的对象,两个对象之间唯一的区别就是,hash值不一样。
但是get(key)时还是正确的,即取出的还是业务逻辑一样的对象的值。
测试代码如下:
package com.collonn.algorithm.test; import java.util.HashMap; import java.util.Random; class Stu { public String name; public int age; public int hash; public Stu() { } public Stu(String name, int age) { this.name = name; this.age = age; this.hash = new Random().nextInt(9); } @Override public boolean equals(Object obj) { if (obj instanceof Stu) { Stu stu = (Stu) obj; if (stu.name.equals(this.name)) { return true; } else { return false; } } return false; } public int hashCode() { return this.hash; } } public class Test2 { private static void Print(Stu s) { System.out.printf("\n name=%s, age=%s, hash=%s", s.name, s.age, s.hash); } public static void main(String[] args) { Stu a = new Stu("a", 1); Stu b = new Stu("a", 2); Stu c = new Stu("a", 3); System.out.printf("\n ------ new ------"); Print(a); Print(b); Print(c); HashMap<Stu, Stu> map = new HashMap<Stu, Stu>(); map.put(a, a); map.put(b, b); map.put(c, c); System.out.printf("\n ------ get ------"); Stu sut = map.get(a); Print(sut); sut = map.get(b); Print(sut); sut = map.get(c); Print(sut); } }
相关文章推荐
- 关于java的equals和hashcode方法
- 【原创】关于java对象需要重写equals方法,hashcode方法,toString方法 ,compareto()方法的说明
- 关于java的equals和hashcode方法
- 关于Java覆盖equals方法时必须覆盖hashCode方法
- 关于为什么要重写hashCode()方法和equals()方法及如何重写
- 关于hashcode和equals方法重写的一些理解!
- java基础解析系列(十一)---equals、==和hashcode方法
- Java-正确使用equals和hashCode方法
- java中equals和hashcode方法
- JAVA中重写equals()方法为什么要重写hashcode()方法说明
- java hashCode()方法和equals()方法
- Java中equals和==的区别?为什么重写equals方法后,一定要重写hashCode方法?
- JAVA中重写equals()方法为什么要重写hashcode()方法?
- JAVA中重写equals()方法的同时要重写hashcode()方法
- Java记录 -57- Object的equals、hashcode和toString方法
- Java中的equals和hashCode方法详解
- Java equals 和 hashcode 方法
- java 关于hashcode和equals的测试
- java集合(3)- Java中的equals和hashCode方法详解
- JAVA中重写equals()方法的同时要重写hashcode()方法