欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

散列与散列码

程序员文章站 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返回的值却相等。)