码迷,mamicode.com
首页 > 其他好文 > 详细

容器深入研究 --- 散列与散列码(一)

时间:2014-07-29 21:55:12      阅读:378      评论:0      收藏:0      [点我收藏+]

标签:java   容器   set   map   散列码   

通常的:

当标准类库中的类被作用HashMap的键。它用的很好,因为它具备了键所需的全部性质

当你自己创建用作HashMap的键的类,有可能会忘记在其中放置必须的方法,而这时通常会犯的一个错误。

例如:考虑一个天气系统,将Groundhog对象与Prediction对象联系起来。

class Groundhog {

	protected int number;
	
	public Groundhog(int n) {
		number = n;
	}
	
	public String toString() {
		return "Groundhog # " + number;
	}
}

class Prediction {
	private static Random rand = new Random(47);
	
	private boolean shadow = rand.nextDouble() > 0.5;
	
	@Override
	public String toString() {
		if (shadow) {
			return "Six more weks of Winter";
		} else {
			return "Early Spring";
		}
	}
}


public class Main {

	public static <T extends Groundhog> void detectSpring(Class<T> type) throws Exception {
		Constructor<T> ghog = type.getConstructor(int.class);
		Map<Groundhog, Prediction> map = new HashMap<Groundhog, Prediction>();
		for (int i = 0; i < 10; i++) {
			map.put(ghog.newInstance(i), new Prediction());
			System.out.println("map : " + map);
		}
		Groundhog 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 {
		map : {Groundhog # 0=Six more weks of Winter}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 1=Six more weks of Winter}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 1=Six more weks of Winter}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 2=Early Spring, Groundhog # 1=Six more weks of Winter}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 4=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 1=Six more weks of Winter}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 4=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 1=Six more weks of Winter, Groundhog # 5=Early Spring}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 4=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 6=Early Spring, Groundhog # 1=Six more weks of Winter, Groundhog # 5=Early Spring}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 4=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 7=Early Spring, Groundhog # 6=Early Spring, Groundhog # 1=Six more weks of Winter, Groundhog # 5=Early Spring}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 4=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 7=Early Spring, Groundhog # 6=Early Spring, Groundhog # 1=Six more weks of Winter, Groundhog # 8=Six more weks of Winter, Groundhog # 5=Early Spring}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 4=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 9=Six more weks of Winter, Groundhog # 7=Early Spring, Groundhog # 6=Early Spring, Groundhog # 1=Six more weks of Winter, Groundhog # 8=Six more weks of Winter, Groundhog # 5=Early Spring}
		Looking up prediction for Groundhog # 3
		Key not found : Groundhog # 3
		
		detectSpring(Groundhog.class);
	}
}

首先会使用Groundhog和与之相关联的Prediction填充HashMap,然后打印此HashMap,以便观察它是否被填入了一些内容。然后使用标识数组3作为Groundhog的主键,查找与之对应的内容。

但是,它无法找到数字3这个键。问题出在Groundhog自动继承基类Object,所以这里使用Object的hashCode()生成散列码,而它默认的使用对象的地址计算散列码。因此,由Groundhog(3)生成的第一个实例的散列码与由Groundhog的第二个实例的散列码是不同的,而我们正是使用的后者进行查找的。

注意的:

可能你会认为,只需要编写恰当的hashCode()方法的覆盖版本即可。但是它仍然无法正常运行,除非你同时覆盖equals()方法,它也是Object的一部分。HashMap使用equals()判断判断当前的键是否与表中的键相同。

正确的equals()方法必须满足下列五个条件:

1,自反性。对任意x,x.equals(x)一定返回true。

2,对称性。对任意x和y,如果y.equals(x)返回true,则x.equals(y)也返回true。

3,传递性。对任意x、y、z,如果有x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)一定返回true。

4,一致性。对任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用多少次x.equals(y),返回的 结果应该保持一致,要么一直是true,要么一直是false。

5,对任何不适null的x,x.equals(null)一定返回false。

再次强调,默认的Object.equals()只是比较对象的地址,所以一个Groundhog(3)并不等于另一个Groundhog(3)。因此,要使用自己的类作为HashMap的键,必须同时重载hashCode()和equals()。

public class Groundhog2 extends Groundhog {
		public Groundhog2(int n) {
			super(n);
		}
		public int hashCode() { reutrn number;}
		public boolean equals(Object o) {
			return o instanceof Groundhog2 && (number == ((Groundhog2)o).number);
		}
	}

另外需要注意的:

Groundhog2.hashCode()返回Groundhog的标识数字作为散列码。在此例中,程序员负责确保不同的Groundhog具有不同的编号。hashCode()并不需要总是返回唯一的标识码,但是equals()方法必须严格的判断两个对象是否相同。尽管看起来equals()方法只是检查其参数是否是Groundhog2的实例,但是instanceof悄悄的检查了此对象是否为null,因为如果instanceof左边的参数为null,它会返回false。

理解hashCode()

前面的例子只是正确解决问题的第一步。它只说明,如果不为你的键覆盖hashCode()和equals(),那么使用散列的数据结构(HashSet,HashMap,LinkedHashSet或LinkedHashMap)就无法正确处理你的键。然而,要很好的解决此问题,你必须了解这些数据结构的内部结构。

首先,使用散列的目的在于:想要使用一个对象来查找另一个对象。不过使用TreeMap或者你自己实现的Map也可以达到此目的。与散列实现相反,下面的示例用一对ArrayList实现了一个Map。

class MapEntry<K,V> implements Map.Entry<K, V> {

	private K key;
	private V value;
	
	public MapEntry(K key, V value) {
		this.key = key;
		this.value = value;
	}
	
	public K getKey() {
		return key;
	}

	public V getValue() {
		return value;
	}

	public V setValue(V value) {
		V result = this.value;
		this.value = value;
		return result;
	}
	
	@Override
	public int hashCode() {
		return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
	}
	
	@Override
	public boolean equals(Object o) {
		if (!(o instanceof MapEntry)) {
			return false;
		}
		MapEntry me = (MapEntry) o;
		return (key == null ? me.getKey() == null : key.equals(me.getKey())) && (value == null ? 
				me.getValue() == null : value.equals(me.getValue()));
	}
	
	@Override
	public String toString() {
		return key + " = " + value;
	}
}


class SlowMap<K,V> extends AbstractMap<K, V> {
	private List<K> keys = new ArrayList<K>();
	private List<V> values = new ArrayList<V>();
	
	public V put(K key, V value) {
		V oldValue = get(key);
		if (!keys.contains(key)) {
			keys.add(key);
			values.add(value);
		} else {
			values.set(keys.indexOf(key), value);
		}
		return oldValue;
	}

	public V get(Object key) {
		if (!keys.contains(key)) {
			return null;
		}
		return values.get(keys.indexOf(key));
	}
	
	@Override
	public Set<java.util.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 class Main {
	public static void main(String[] args) throws Exception {
		{ANGOLA=Luanda, CAPE VERDE=Praia, EGYPT=Cairo, BURUNDI=Bujumbura, BENIN=Porto-Novo, ALGERIA=Algiers, CAMEROON=Yaounde, CONGO=Brazzaville, CENTRAL AFRICAN REPUBLIC=Bangui, EQUATORIAL GUINEA=Malabo, COMOROS=Moroni, DJIBOUTI=Dijibouti, BURKINA FASO=Ouagadougou, CHAD=N'djamena, BOTSWANA=Gaberone}
		null
		[ANGOLA = Luanda, CAPE VERDE = Praia, EGYPT = Cairo, BURUNDI = Bujumbura, BENIN = Porto-Novo, ALGERIA = Algiers, CAMEROON = Yaounde, CONGO = Brazzaville, CENTRAL AFRICAN REPUBLIC = Bangui, EQUATORIAL GUINEA = Malabo, COMOROS = Moroni, DJIBOUTI = Dijibouti, BURKINA FASO = Ouagadougou, CHAD = N'djamena, BOTSWANA = Gaberone]
		SlowMap<String, String> m = new SlowMap<String, String>();
		m.putAll(Countries.capitals(15));
		System.out.println(m);
		System.out.println(m.get("BULGARIA"));
		System.out.println(m.entrySet());
	}
}

Map.entrySet()方法必须产生一个Map.Entry对象集。但是,Map.Entry是一个接口,用来描述依赖于实现的结构,因此如果你想要创建自己的Map类型,就必须同时定义Map.Entry的实现。

容器深入研究 --- 散列与散列码(一),布布扣,bubuko.com

容器深入研究 --- 散列与散列码(一)

标签:java   容器   set   map   散列码   

原文地址:http://blog.csdn.net/biezhihua/article/details/38277315

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!