《Effective Java》中推薦的hashCode算法

http://blog.csdn.net/error_case/article/details/46503103java


Google首席Java架構師Joshua Bloch在他的著做《Effective Java》中提出了一種簡單通用的hashCode算法:

1. 初始化一個整形變量,爲此變量賦予一個非零的常數值,好比 int result = 17 ;
2. 選取equals方法中用於比較的全部域,而後針對每一個域的屬性進行計算:
  (1) 若是是boolean值,則計算 f ? 1:0
  (2) 若是是byte\char\short\int,則計算 (int)f
  (3) 若是是long值,則計算(int)( f ^ (f >>> 32) )
  (4) 若是是float值,則計算 Float.floatToIntBits(f)
  (5) 若是是double值,則計算 Double.doubleToLongBits(f) ,而後返回的結果是long,再用規則(3)去處理long,獲得int
  (6) 若是是對象應用,若是equals方法中採起遞歸調用的比較方式,那麼hashCode中一樣採起 遞歸調用hashCode的方式 。不然須要爲這個域計算一個範式,好比當這個域的值爲null的時候,那麼hashCode 值爲0
  (7) 若是是數組,不必本身去從新遍歷一遍數組, java.util.Arrays.hashCode 方法包含了8種基本類型數組和引用數組的hashCode計算,算法同上,
  java.util.Arrays.hashCode(long[])的具體實現:
[java]  view plain copy
  1. public static int hashCode(long a[]) {   
  2.         if (a == null)   
  3.             return 0;   
  4.     
  5.         int result = 1;   
  6.         for (long element : a) {   
  7.             int elementHash = (int)(element ^ (element >>> 32));   
  8.             result = 31 * result + elementHash;   
  9.         }   
  10.     
  11.         return result;   
  12. }   

Arrays.hashCode(...)只會計算一維數組元素的hashCOde,若是是多維數組,那麼須要遞歸進行hashCode的計算,那麼就須要使用Arrays.deepHashCode(Object[])方法。

3. 最後,要如同上面的代碼,把每一個域的散列碼合併到result當中: result = 31 * result + elementHash ;
4. 測試,hashCode方法是否符合文章開頭說的基本原則,這些基本原則雖然不能保證性能,可是能夠保證不出錯。

這個算法存在這麼幾個問題須要探討:
1. 爲何初始值要使用非0的整數?這個的目的主要是爲了減小hash衝突,考慮這麼個場景,若是初始值爲0,而且計算hash值的前幾個域hash值計算都爲0,那麼這幾個域就會被忽略掉,可是初始值不爲0,這些域就不會被忽略掉,示例代碼:
[java]  view plain copy
  1. import java.io.Serializable;   
  2.  public class Test implements Serializable {   
  3.     
  4.     private static final long serialVersionUID = 1L;   
  5.     
  6.     private final int[] array;   
  7.     
  8.     public Test(int... a) {   
  9.         array = a;   
  10.     }   
  11.     
  12.     @Override  
  13.     public int hashCode() {   
  14.         int result = 0//注意,此處初始值爲0         
  15.        for (int element : array) {   
  16.             result = 31 * result + element;   
  17.         }   
  18.         return result;   
  19.     }   
  20.     
  21.     public static void main(String[] args) {   
  22.         Test t = new Test(0000);   
  23.         Test t2 = new Test(000);   
  24.         System.out.println(t.hashCode());   
  25.         System.out.println(t2.hashCode());   
  26.     }   
  27.     
  28. }   

若是hashCode中result的初始值爲0,那麼對象t和對象t2的hashCode值都會爲0,儘管這兩個對象不一樣。但若是result的值爲17,那麼計算hashCode的時候就不會忽略這些爲0的值,最後的結果t1是15699857,t2是506447

2. 爲何每次須要使用乘法去操做result? 主要是爲了使散列值依賴於域的順序 ,仍是上面的那個例子,Test t = new Test(1, 0)跟Test t2 = new Test(0, 1), t和t2的最終hashCode返回值是不同的。

3. 爲何是31? 31是個神奇的數字,由於任何數n * 31就能夠被JVM優化爲 (n << 5) -n ,移位和減法的操做效率要比乘法的操做效率高的多。

例子:算法

 
import java.util.Arrays;
import java.util.List;
import java.util.Map;


public class HashTest {

	 int intVar;
	 long longVar;
	 boolean booleanVar;
	 float floatVar;
	 double doubleVar;
	 byte byteVar;
	 String stringVar;
	 Object objectVar;
	 A aVar;
	 List<A> listVar;
	 Map<String, A> mapVar;
	 long[] longArrayVar;
	 A[] aArrayVar;
	 HashTest hashTestVar;
	 HashTest[] hashTestArrayVar;
	 
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((aVar == null) ? 0 : aVar.hashCode());
		result = prime * result + (booleanVar ? 1231 : 1237);
		result = prime * result + byteVar;
		long temp;
		temp = Double.doubleToLongBits(doubleVar);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		result = prime * result + Float.floatToIntBits(floatVar);
		result = prime * result + Arrays.hashCode(hashTestArrayVar);
		result = prime * result
				+ ((hashTestVar == null) ? 0 : hashTestVar.hashCode());
		result = prime * result + intVar;
		result = prime * result + ((listVar == null) ? 0 : listVar.hashCode());
		result = prime * result + Arrays.hashCode(longArrayVar);
		result = prime * result + (int) (longVar ^ (longVar >>> 32));
		result = prime * result + ((mapVar == null) ? 0 : mapVar.hashCode());
		result = prime * result
				+ ((objectVar == null) ? 0 : objectVar.hashCode());
		result = prime * result + Arrays.hashCode(aArrayVar);
		result = prime * result
				+ ((stringVar == null) ? 0 : stringVar.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;
		HashTest other = (HashTest) obj;
		if (aVar == null) {
			if (other.aVar != null)
				return false;
		} else if (!aVar.equals(other.aVar))
			return false;
		if (booleanVar != other.booleanVar)
			return false;
		if (byteVar != other.byteVar)
			return false;
		if (Double.doubleToLongBits(doubleVar) != Double
				.doubleToLongBits(other.doubleVar))
			return false;
		if (Float.floatToIntBits(floatVar) != Float
				.floatToIntBits(other.floatVar))
			return false;
		if (!Arrays.equals(hashTestArrayVar, other.hashTestArrayVar))
			return false;
		if (hashTestVar == null) {
			if (other.hashTestVar != null)
				return false;
		} else if (!hashTestVar.equals(other.hashTestVar))
			return false;
		if (intVar != other.intVar)
			return false;
		if (listVar == null) {
			if (other.listVar != null)
				return false;
		} else if (!listVar.equals(other.listVar))
			return false;
		if (!Arrays.equals(longArrayVar, other.longArrayVar))
			return false;
		if (longVar != other.longVar)
			return false;
		if (mapVar == null) {
			if (other.mapVar != null)
				return false;
		} else if (!mapVar.equals(other.mapVar))
			return false;
		if (objectVar == null) {
			if (other.objectVar != null)
				return false;
		} else if (!objectVar.equals(other.objectVar))
			return false;
		if (!Arrays.equals(aArrayVar, other.aArrayVar))
			return false;
		if (stringVar == null) {
			if (other.stringVar != null)
				return false;
		} else if (!stringVar.equals(other.stringVar))
			return false;
		return true;
	}
		 
}


class A{
	
	int a;
	long b;
	String c;
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + a;
		result = prime * result + (int) (b ^ (b >>> 32));
		result = prime * result + ((c == null) ? 0 : c.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;
		A other = (A) obj;
		if (a != other.a)
			return false;
		if (b != other.b)
			return false;
		if (c == null) {
			if (other.c != null)
				return false;
		} else if (!c.equals(other.c))
			return false;
		return true;
	}
	
	
}
相關文章
相關標籤/搜索