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

通常的:

当标准类库中的类被作用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

时间: 2024-10-26 14:43:19

容器深入研究 --- 散列与散列码(一)的相关文章

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

如何覆盖hashCode(): 明白了如何散列之后,编写自己的hashCode()就更有意义了. 首先,你无法控制bucket数组的下标值的产生.这个值依赖于具体的HashMap对象的容量,而容量的改变与容器的充满程度和负载因子有关.hashCode()生成的结果,经过处理后称为桶位的下标. 设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值.如果在将一个对象用put()添加进HashMap时产生一个hashCode()值,而用get()

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

为速度而散列: SlowMap.java说明了创建一个新的Map并不困难.但正如它的名称SlowMap所示,它不会很快,如果有更好的选择就应该放弃它.它的问题在于对键的查询,键没有按照任何特定的顺序保存,所以只能使用简单的线性查询,而线性查询是最慢的查询方式. 散列的价值在于速度: 散列使得查询得以快速进行.由于瓶颈在于键的查询速度,因此解决方案之一就是保持键的排序状态,然后使用Collections.binarySearch()进行查询. 散列则更进一步,它将键保存在某处,以便能够很快的找到.

Java散列和散列码的实现

转自:https://blog.csdn.net/al_assad/article/details/52989525 散列和散列码 ※正确的equals方法应该满足的的条件: ①自反性:x.equals(x) 一定返回true: ②对称性:y.euqlas(x)为true,那么x.equals(y)一定为true: ③传递性:x.equals(y)为true,y.euqlas(z)为true,则z.equals(x)为true: ④一致性:如果x,y中用于等价比较的信息没有变化,那么无论调用y.

java 散列与散列码探讨 ,简单HashMap实现散列映射表运行各种操作示列

package org.rui.collection2.maps; /** * 散列与散列码 * 将土拔鼠对象与预报对象联系起来, * @author lenovo * */ //土拨鼠 public class Groundhog { protected int number; public Groundhog(int n) { number=n; } @Override public String toString() { return "Groundhog #" + number

java 散列与散列码探讨 ,简单HashMap实现散列映射表执行各种操作示列

package org.rui.collection2.maps; /** * 散列与散列码 * 将土拔鼠对象与预报对象联系起来, * @author lenovo * */ //土拨鼠 public class Groundhog { protected int number; public Groundhog(int n) { number=n; } @Override public String toString() { return "Groundhog #" + number

【ThinkingInJava】51、散列与散列码

/** * 书本:<Thinking In Java> * 功能:散列与散列码 * 文件:Groundhog.java * 时间:2015年5月3日09:42:54 * 作者:cutter_point */ package Lesson17Containers; public class Groundhog { protected int number; //保护类型,继承之后还是保护类型 public Groundhog(int n) { number = n; } public Strin

数据结构--散列排序--散列表

散列表 散列查找,我们又回到了查找, 编译的时候,涉及变量及属性的管理: 插入:新变量的定义 查找:变量的引用 实际上是动态查找问题,查找树AVL树. 两个变量名(字符串)比较效率不高.字符串的比较要一个一个的比下去,时间会比较长, 是否可以把字符串转换成数字,再处理,就快多了.就是散列查找的思想. 已知的查找方法: 顺序查找                                          O(N) 二分查找(静态查找,不适合动态查找)   O(log2N) 二叉搜索数    

字典:散列表、散列字典、关键字列表、集合与结构体

字典 散列表和散列字典都实现了Dict的行为.Keyword模块也基本实现了,不同之处在于它支持重复键. Eunm.into可以将一种类型的收集映射转化成另一种. defmodule Sum do def values(dict) do dict |> Dict.values |> Enum.sum end end hd = [ one: 1, two: 2, three: 3 ] |> Enum.into HashDict.new IO.puts Sum.values(hd) #=&g

容器深入研究 --- 理解Map

通常的: 映射表(也称关联数组)的基本思想是它维护的键-值(对)关联,因此你可以使用键来查找值. 标准的Java类库中包含了Map的几种实现,包括:HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap. 它们都有同样的基本接口Map,但是行为特性各不相同,这表现在效率.键值对的保存及呈现次序.对象的保存周期.映射表如何在多线程程序中工作的判定"键"等价的策略方面.Map接口实现的数量应该让