• 《Effective Java》中推荐的hashCode算法


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


    Google首席Java架构师Joshua Bloch在他的著作《Effective Java》中提出了一种简单通用的hashCode算法:

    1. 初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result =17;
    2. 选取equals方法中用于比较的所有域,然后针对每个域的属性进行计算:
      (1) 如果是boolean值,则计算f ? 1:0
      (2) 如果是bytecharshortint,则计算(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 plaincopy
    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 plaincopy
    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;
    	}
    	
    	
    }
    


  • 相关阅读:
    win7常用快捷键
    java中构造代码块、方法调用顺序问题
    eclipse项目改为maven项目导致svn无法比较历史数据的解决办法
    linux配置Anaconda python集成环境
    DataFrame对行列的基本操作实战
    驱动:电阻屏触摸芯片NS2009
    读书笔记:代码大全(第二版)
    资料:磁角度传感器芯片
    经验:FatFs文件系统实时写入
    笔记:CAN收发器-TJA1051T与TJA1051T/3调试总结
  • 原文地址:https://www.cnblogs.com/leeeee/p/7276024.html
Copyright © 2020-2023  润新知