HashMap和HashSet深度解析

小弟季義欽發表於2012-10-31
package container;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;

/**
 * @author jiq408694711@163.com
 *  
 *  測試HashSet中,通過equals方法檢測到重複之後會發生什麼。
 *  以及測試hashCode函式生成的雜湊碼相同之後,會不會被去掉。
 *
 */
class TestObject{
	int x;
	int y;
	public TestObject(int x, int y){
		this.x = x;
		this.y = y;
	}
	
	/* 一般重寫equals方法的同時也會重寫hasCode方法,為什麼? */
	public int hashCode(){
		return x;
	}
	
	/* 注意引數是Object型別,否則就不是覆蓋了 */
	public boolean equals(Object obj) {
		TestObject oo = (TestObject)obj;
		return (this.x == oo.x)?true:false;
	}
}

public class HashSetTest {

	public static void main(String args[]){
		/**
		 * 重寫了Object的equals方法,只要兩個物件x欄位相等,
		 * 那麼兩個物件就相等。(預設的equals方法是比較引用==)
		 * (注意: 引用仍然不同,即不是指向同一個物件)
		 */
		TestObject o1 = new TestObject(1,3);
		TestObject o2 = new TestObject(3,3);
		System.out.println(o1.equals(o2));
		System.out.println(o1 == o2);
		
		/**
		 *  HashMap,採用陣列雜湊儲存,雜湊值有hashCode返回,
		 *  同一個hashCode的物件,採用連結法解決衝突。通過equals判斷是否衝突。
		 *  下面是HashMap內部的put方法的實現。
		 * 	public V put(K key, V value) {
		        if (key == null)
		            return putForNullKey(value);
		        int hash = hash(key.hashCode()); 		//生成hash值
		        int i = indexFor(hash, table.length);	//找到該hash值在陣列中位置索引
		       
		        //迴圈該hash槽的所有物件
		        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
		            Object k;
		            //如果equals()方法返回true,發現“重複”
		            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
		                V oldValue = e.value;
		                e.value = value; 	//將新的值替換原來的值,鍵不動
		                e.recordAccess(this);
		                return oldValue;
		            }
		        }

		        modCount++;
		        addEntry(hash, key, value, i);
		        return null;
    		}
		 * */	
		HashMap<TestObject,String> map = new HashMap<TestObject,String>();
		//鍵hashCode相同,且equals返回true,故替換值,可以看到鍵沒有變化
		map.put(new TestObject(1,0), "XXX");	
		map.put(new TestObject(1,2), "YYY");
		System.out.println("MAP SIZE:"+map.size());
		for(Entry<TestObject,String> e: map.entrySet()){
			System.out.println(e.getKey().x+","+e.getKey().y+","+e.getValue());
		}
		
		/**
		 *  HashSet通過HashMap實現,鍵就是插入的物件,而值統一是一個static的Object
		 *  下面是add方法的實現。
		 * 	public boolean add(E e) {
				return map.put(e, PRESENT)==null;
    		}
		 * */
		HashSet<TestObject> set = new HashSet<TestObject>();
		set.add(new TestObject(1,2));
		//hashCode方法和equals方法都返回相同值,所以替換值,但是它的值部分只是一個static的Object
		set.add(new TestObject(1,1));
		set.add(new TestObject(3,1));
		set.add(new TestObject(3,4));
		
		System.out.println("SET SIZE:"+set.size());
		for(TestObject o: set){
			System.out.println(o.x+","+o.y);
		}
		
		/*
		 * 注意,HashCode返回的值決定了你的存在雜湊表中的Entry物件應該在哪個槽位
		 * 設想如果你的equals方法重寫了,認為兩個物件相等,但是hashCode返回的兩個物件
		 * 的值卻不一樣,那麼他們都不可能在一個槽位,所以equals方法就沒意義。
		 * 
		 * 所以我們重寫鍵物件的equals方法的時候,一般同時重寫hashCode方法。
		 * 儘量保證: 
		 * (1)當obj1.equals(obj2)為true時,obj1.hashCode() == obj2.hashCode()必須為true
		 * (2)當obj1.hashCode() == obj2.hashCode()為false時,obj1.equals(obj2)必須為false
		 */
		
		/**
		 * 擴充: 如果吧TestObject的equals方法改成return (this.y == oo.y)?true:false;
		 * 那麼	set.add(new TestObject(1,2));
				set.add(new TestObject(1,1));
				set.add(new TestObject(3,1));
				會發生什麼?輸出set大小是多少?答案是3
		 * 	因為前兩個物件雖然hashCode相同,但是equals返回false,故不是衝突的物件
		 *  後兩個雖然equals返回了true,但是不是存在同一個槽位的物件,故也不是衝突物件。
		 * */
	}
}

輸出:

false
false
MAP SIZE:1
1,0,YYY
SET SIZE:2
1,2
3,1

相關文章