hashCode与equals源码分析与应用

时间:2022-07-19 16:13:12
参考

1、JDK1.8源码

2、张孝祥系列

3、《数据结构高分笔记》- 率辉

场景

hashCode到底怎么理解

分析
基本理论

有这样一个场景:怎么从集合中查找某一个具体的元素?假设集合由一万个元素组成,如果一个一个从头到尾依次对比查询,显然太慢 - 当然这也是一种查找方法。常用查找方法大致可分为三大类:顺序结构查找类型、还有基于二叉树的分支结构查询类型以及基于Hash Table的hash查找类型。我们来简单来理解以下hash table的建表过程,及其查询方式,这里假设 Hash函数为 hash(int  key)= key mod 13 (注: 13代表hash table的表长度,对应java中集合类的size)。

hash table 构造过程
关键字 用Hash函数计算地址 地址
7 7 mod 13 7
4 4 mod 13 4
134 134 mod 13 4
14 14 mod 13 1


构造好的 hash table
地址 0 1 2 3 4 5 6 7 8 9 10 11 12
关键字   14     4、134     7          

由上表可知,存在 关键字 不同 而hash后的地址相同。有了以上认知,接下理解hash函数就不难了。

hashCode分析与内存泄漏模拟
  • Object与String中的hashCode()与equals()分析

     /**
* 作用:计算对象的散列值(又叫 关键字),散列表根据对象的散列值与散列函数能直接计算出该对象在内存中的地址(查询
* 的时间复杂度为常量)
* 规则:
* 1、if(A.equals(B)) then 必有 B.hashCode() == A.hashCode // Object A,B
* 2、A、B 不equals,A、B的散列值可能相同。但是让不等的对象具有不同的hash值能提高基于hash算法的对象查找性能
* “ However, the
* programmer should be aware that producing distinct integer results
* for unequal objects may improve the performance of hash tables.” 即 尽量减小“冲突”的发生。
*/
public native int hashCode();

/**
* 作用:比较两对象是否相等 - a 与 b 指向同一个对象时(内存中只有一份),a.equals(b) 返回 true
* 规则:
* 1、x.equals(y) <=> y.equals(x)
* 2、重写 equals方法同时,通常需要重写hashCode方法 - 这是hashCode申明的协议!
*/
public boolean equals(Object obj)
{
return (this == obj);
}

/**
* String 类的hashCode实现
* 实现方式:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] // s[i] : 字符串中的字符, n: 长度
*
* @return a hash code value for this object.
*/
public int hashCode()
{
int h = hash; // 默认为空
if (h == 0 && value.length > 0)
{
char val[] = value; // 若String str = "abc" ,则 val [] = ['a','b','c']
for (int i = 0; i < value.length; i++)
{
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

/**
* 作用:判断字符串是否相等
* 方式:如果串中的每个字符的ASC码相等则,两字符串相等
*/
public boolean equals(Object anObject)
{
if (this == anObject) { return true;}
if (anObject instanceof String)
{
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length)
{
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0)
{
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

  • 基于HashSet的内存泄漏模拟

基于Hash的集合对象(eg、HashSet), 若插入后,再改变成员变量的值,就可能导致内存泄漏。这里以HashSet中add(E)为例,先从宏观上分析一下Hash查找过程(add与remove的源码分析后续补上)。前提: Hash table已经构造好
1、获取对象的关键字(即hashCode值)
2、根据关键字代入 hash函数,计算该对象的内存地址
3、根据地址对比该对象是否已经在相关内存区域中存在?存在返回false,插入失败,否则成功。

/**
* @author pengyucheng
* 实体类
*/
public class ReflectPoint
{
private int x;
private int y;
public ReflectPoint(int x, int y)
{
super();
this.x = x;
this.y = y;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ReflectPoint other = (ReflectPoint) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
                /** *  内存泄漏  */HashSet<ReflectPoint> collection2 = new HashSet<ReflectPoint>();ReflectPoint rp11= new ReflectPoint(1,2);ReflectPoint rp22= new ReflectPoint(2,2);ReflectPoint rp44= new ReflectPoint(2,2);collection2.add(rp11);collection2.add(rp22);collection2.add(rp44);System.out.println(collection2.size());// size = 2rp22.setX(9);/* *rp22成员变量 x 值已经改变 => hashCode值改变 =>查找所依据的地址改变 =>可能查找到,有可能查找不到 *若没有找到,则remove不了 - 这就导致了内存泄漏 ! /collection2.remove(rp22);// System.out.println(collection2.size()); // size = 2 => rp22所指向的对象没有被删除 => 内存泄漏

总结

作用:hashCode是为了提高基于Hash table类的对象查找效率而存在。

规则:重写equals方法时需要同步重写hashCode方法; 在hash类集合中增加元素后,不要对元素的值进行修改,否则可能导致内存泄漏