散列与散列码
程序员文章站
2022-03-01 20:44:03
...
Demo:
按照你的想象,我们应该是可以查出来Groudhog gh = ghog.newInstance(3);这个key的,但是没查到
package cn.partner4java.hashcode;
/**
* 每个Groudhog被给予一个标识数字
* @author partner4java
*
*/
public class Groudhog {
protected int number;
public Groudhog(int number) {
this.number = number;
}
@Override
public String toString() {
return "Groudhog #" + number;
}
}
package cn.partner4java.hashcode;
import java.util.Random;
/**
* Predition类包含一个boolean值和一个toString方法。
* boolean用java.util.randrom()来初始化
* @author partner4java
*
*/
public class Prediction {
private static Random random = new Random(47);
private boolean shadow = random.nextDouble() > 0.5;
@Override
public String toString() {
if(shadow){
return "Six more weeks of Winter!";
}else{
return "Early Spring";
}
}
}
package cn.partner4java.hashcode;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
/**
* detectSpring方法使用反射机制来实例化及使用Groundhog类或任何从Groundhog派生来的类。
* @author partner4java
*
*/
public class SpringDetector {
public static <T extends Groudhog> void detectSpring(Class<T> type) throws Exception{
Constructor<T> ghog = type.getConstructor(int.class);
Map<Groudhog, Prediction> map = new HashMap<Groudhog, Prediction>();
for(int i=0;i<10;i++){
map.put(ghog.newInstance(i), new Prediction());
}
System.out.println("map = " + map);
Groudhog gh = ghog.newInstance(3);
System.out.println("Looking up prediction for " + gh);
if(map.containsKey(gh)){
System.out.println(map.get(gh));
}else{
System.out.println("key not found: " + gh);
}
}
public static void main(String[] args) throws Exception {
detectSpring(Groudhog.class);
}
}
问题出在Groundhog自动的继承自基类Object,所以这里使用Object的hashCode方法生成散列码,而他默认是使用对象的地址计算散列码。
因此两个Groundhog(3)的散列码不同。
但是,你覆盖hashCode的同时也必须覆盖equals()方法,他也是Object的一部分。
HashMap使用equals()判断当前的键是否与表中存在的键相同。
正确的equals方法必须满足下列5个条件:
1、自反性(reflexive):对于任何非null的引用值x,x.equals(x)必须返回true。
如果违背,当你把该类的实例添加到集合(collection)中,然后该集合的contains会告诉你,没有包含你刚刚添加的实例。
2、对称性(symmetric):对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
一个实例对比不区分了大小写,但是,反过来对比是区分的,就导致了不对称。
3、传递性(transitive):对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
子类增加的信息会影响到equals的比较结果。
4、一致性(consistent):对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true,或者一致的返回false。
都不要是equals方法依赖于不可靠的资源。
5、非空性(Non-nullity):对于任何非null的引用值x,x.equals(null)必须返回false。
默认的equals方法是比较的内存地址。
为Groundhog添加hashCode和equals方法:
package cn.partner4java.hashcode;
/**
* 每个Groudhog被给予一个标识数字
* @author partner4java
*
*/
public class Groudhog {
protected int number;
public Groudhog(int number) {
this.number = number;
}
@Override
public String toString() {
return "Groudhog #" + number;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + number;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Groudhog other = (Groudhog) obj;
if (number != other.number)
return false;
return true;
}
}
现在就可以查找到了。
参考:
http://blog.csdn.net/partner4java/article/details/7066349
http://blog.csdn.net/partner4java/article/details/7067919
------------------
理解hashCode()
使用散列的目的在于:想要使用一个对象来查找另外一个对象。不过使用TreeMap或者你自己使用Map也可以达到此目的。
Demo:
package cn.partner4java.hashcode;
import java.util.Map.Entry;
/**
* Map.entrySet()方法必须产生一个Map.Entry<K, V>对象集。但是,Map.Entry是一个接口,用来描述依赖于实现的结构,因此如果你想要创建自己的Map类型。
* @author partner4java
*
* @param <K>
* @param <V>
*/
public class MapEntry<K, V> implements Entry<K, V> {
private K key;
private V value;
public MapEntry(K key, V value) {
super();
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
// public void setKey(K key) {
// this.key = key;
// }
public V getValue() {
return value;
}
public V setValue(V v) {
V result = value;
value = v;
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MapEntry other = (MapEntry) obj;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
@Override
public String toString() {
return "MapEntry [key=" + key + ", value=" + value + "]";
}
}
package cn.partner4java.hashcode;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.mindview.util.Countries;
/**
* 与散列相反,用一对ArrayList实现了一个Map。
* put()方法只是将键与值放入相应的ArrayList。为了与Map接口保持一致,他必须返回旧的键,或者在没有任何旧键的情况下返回null。
* 如果键存在,他将被用来查找表示他在keys列表中的位置数值型索引,并且这个数字被用作索引来产生与values列表相关联的值。
* @author partner4java
*
* @param <K>
* @param <V>
*/
public class SlowMap<K, V> extends AbstractMap<K, V> {
private List<K> keys = new ArrayList<K>();
private List<V> values = new ArrayList<V>();
public V get(Object key){
if(!keys.contains(key)){
return null;
}
return values.get(keys.indexOf(key));
}
public V put(K key,V value){
V oldValue = get(key); //The old value or null
if(!keys.contains(key)){
keys.add(key);
values.add(value);
}else{
values.set(keys.indexOf(key), value);
}
return oldValue;
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> set = new HashSet<Map.Entry<K,V>>();
Iterator<K> ki = keys.iterator();
Iterator<V> vi = values.iterator();
while(ki.hasNext()){
set.add(new MapEntry<K, V>(ki.next(),vi.next()));
}
return set;
}
public static void main(String[] args) {
SlowMap<String, String> m = new SlowMap<String, String>();
m.putAll(Countries.capitals(15));
System.out.println(m);
System.out.println(m.get("BENIN"));
System.out.println(m.entrySet());
}
}
----------------
为速度而散列:
(前提宗旨是使用数组,因为比线程查找要快)
散列的价值在于速度:散列是的查询得以快速的进行。由于瓶颈位于键的查询速度,因此解决方案之一就是保持键的排序状态,然后使用Collections.binarySearch()进行查询。
散列则更进一步,他讲健保持在某一处,以便能够很快的查找。他使用数组保持了键生成的一个数字,将其作为数组的下标。这个数组就是散列码。
为解决数组容量被固定的问题,不同的键可以产生相同的下标。
于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。一般,如果散列码相同冲突,由外部链接处理:数组并不直接保持值,而是保持值的list。然后对list的值使用equals方法进行线性的查询。这部分的查询会比较慢,但是如果散列函数好的话,数组的每个位置的值会很少。因此,不是查整个list,而是立刻跳到数组的某个位置。这便是HashMap会比较快的原因。
Demo:
package cn.partner4java.hashcode;
import java.util.AbstractMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import net.mindview.util.Countries;
public class SimpleHashMap<K, V> extends AbstractMap<K, V> {
// Choose a prime number for the hash table
// size,to achieve a uniform distribution
static final int SIZE = 997;
// You can't have a physical array of generics,
// but you can upcast to one;
@SuppressWarnings("unchecked")
LinkedList<MapEntry<K, V>>[] buckets = new LinkedList[SIZE];
@Override
public V get(Object key) {
int index = Math.abs(key.hashCode()) % SIZE;
if (buckets[index] == null)
return null;
for (MapEntry<K, V> iPair : buckets[index]) {
if (iPair.getKey().equals(key)) {
return iPair.getValue();
}
}
return null;
}
@Override
public V put(K key, V value) {
V oldValue = null;
int index = Math.abs(key.hashCode()) % SIZE;
if (buckets[index] == null)
buckets[index] = new LinkedList<MapEntry<K, V>>();
LinkedList<MapEntry<K, V>> bucket = buckets[index];
MapEntry<K, V> pair = new MapEntry<K, V>(key, value);
boolean found = false;
ListIterator<MapEntry<K, V>> it = bucket.listIterator();
while (it.hasNext()) {
MapEntry<K, V> iPair = it.next();
if (iPair.getKey().equals(key)) {
oldValue = iPair.getValue();
it.set(pair); // Replace old with new
found = true;
break;
}
}
if (!found)
buckets[index].add(pair);
return oldValue;
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> set = new HashSet<Map.Entry<K, V>>();
for (LinkedList<MapEntry<K, V>> bucket : buckets) {
if (bucket == null)
continue;
for (MapEntry<K, V> mpair : bucket)
set.add(mpair);
}
return set;
}
public static void main(String[] args) {
SimpleHashMap<String, String> m = new SimpleHashMap<String, String>();
m.putAll(Countries.capitals(25));
System.out.println(m);
System.out.println(m.get("ERITREA"));
System.out.println(m.entrySet());
}
}
---------------
覆盖hashCode()
每个覆盖了equals方法的类中,也必须覆盖hashCode方法。
如果不这样的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap、HashSet和Hashtable。
在引用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一的返回同一个整数。在一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。
如果连个对象根绝equals方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
如果两个对象根据equals方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。(比如,当你一个entity只根据id比较是否相等,但是在没实例化之前,没有id数值,那么默认的equals返回false,但是hashCode返回的值却相等。)
上一篇: php怎么实现数组分页
下一篇: 数组相关算法题