散列与散列码

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返回的值却相等。) 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值