散列函数 一个把查找表中的关键字映射成该关键字对应的地址的函数,记做Hash(key)=Addr(这里的地址可能为数组下标、索引、或者内存地址)
冲突 散列表可能把两个或者两个以上的不同关键字映射到同一地址,这种情况成为冲突
散列表 根据关键字直接进行访问的数据结构(散列表建立了关键字与存储地址之间的映射关系)
散列表构造方法
1>散列表定义域需包含全部需要存储的关键字
2>散列函数计算的地址应该等概率、均匀的分布整个地址空间
3>散列函数应尽量简单,在较短时间内计算出任意关键字对应的散列地址
常见的几种散列函数:
1 直接地址法
直接取关键字的某个线性函数值为散列地址,散列函数为:
H(key) = a*key+b
这种计算方法简单,并且不会产生冲突,适合关键字的分布基本连续,若关键字不连续,容易造成较多空位,造成存储空间的浪费。
2除留余数法
假设散列表表长为m,取一个不大于m但接近或等于m的质数p,利用一下公式
H(key)=key%p
除留余数法要选好p,使得每个关键字通过该函数转换后等概率映射到散列空间的任意地址。
3数字分析法
4平方取中法
5折叠法
下面谈下java中的hashcode()
总结几个关键点:
1、HashCode的存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的
2、如果两个对象equals相等,那么这两个对象的HashCode一定也相同
3、如果对象的equals方法被重写,那么对象的HashCode方法也尽量重写
4、如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置
例子:
1 假设内存中有8个位置,1、2、3、4、5、6、7、8,将数据A 存入8个位置中,如果随机存放,查找就从头遍历,
2 如果使用HashCode则效率会快很多,把A的HashCode%8,然后把ID存放在取得余数的那个位置
3 如果A的 HashCode%8算出来的位置上本身已经有数据了。这就取决于算法的实现了,比如ThreadLocal中的做法就是从算出来的位置向后查找第一个为空的位置,放置数据;HashMap的做法就是通过链式结构连起来。反正,只要保证放的时候和取的时候的算法一致就行了。
4、如果ID的 HashCode%8相等怎么办(这种对应的是第三点说的链式结构的场景)?这时候就需要定义equals了。先通过HashCode%8来判断类在哪一 个位置,再通过equals来在这个位置上寻找需要的类。对比两个类的时候也差不多,先通过HashCode比较,假如HashCode相等再判断 equals。如果两个类的HashCode都不相同,那么这两个类必定是不同的。
还有一个是set的例子
我们都知道List 存放的元素是有序可以重复的,而Set存放的是无序且不重复的
但是Set怎样实现不重复呢,如果Set中数据很多,则调用equals方法效率很低,但是HashSet底层基于HashCode,我们向集合中添加一个元素,
首先调用Hashcode方法,定位到一个位置,如果该位置为空,则直接插入。反之注意比较,先比较Hashcode是否相同,相同的话不存。Hashcode不同则通过equals方法比较
如果不同则插入该元素。
再谈重写Object的equals(Object obj)方法为什么尽量要重写Object的hashCode()方法
首先
public class HashCodeClass
{
private String str0;
private double dou0;
private int int0;
public boolean equals(Object obj)
{
if (obj instanceof HashCodeClass)
{
HashCodeClass hcc = (HashCodeClass)obj;
if (hcc.str0.equals(this.str0) &&
hcc.dou0 == this.dou0 &&
hcc.int0 == this.int0)
{
return true;
}
return false;
}
return false;
}
}
打印出复写equals方法后的该类的hashcode
public class TestMain
{
public static void main(String[] args)
{
System.out.println(new HashCodeClass().hashCode());
System.out.println(new HashCodeClass().hashCode());
System.out.println(new HashCodeClass().hashCode());
System.out.println(new HashCodeClass().hashCode());
System.out.println(new HashCodeClass().hashCode());
System.out.println(new HashCodeClass().hashCode());
}
}
值为
1901116749
1807500377
355165777
1414159026
1569228633
778966024
如上述代码没有复写hashCode值,那么将几个类存入Set中由于hashCode不相同,则set中存入多个HashCodeClass,这样不合理,所以要复写Hashcode()
Integer 复写Hashcode()
public int hashCode() {
return value;
}
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
String复写Hashcode()
public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
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;
}
entity 复写Hashode()
public final int hashCode() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}