Java集合(7):散列与散列码
散列的价值在于速度。我们使用数组来保存键的信息,这个信息并不是键本身,而是通过键对象生成一个数字(散列码),作为数组下标。由于数组的容量是固定的,而散列容器的大小是可变的,所以不同的键可以产生相同的数组下标(散列码)。也就是说,可能会有冲突(当然也有特例,比如EnumMap和EnumSet)。所以,数组的值存放着一个保存所有相同散列码的值的list(引用)。然后对list中的值使用equals进行线性查询。如果散列函数设计的比较好的话,数组的每个位置只有较少的值,并且浪费空间也小。于是,查询过程就是首先计算键的散列码得到数组下标,然后内存寻址(时间复杂度为O(1),赋值)找到数组的值,再遍历list(时间复杂度为O(n),线性查询)即可。即hashCode和equals共同确定了对象的唯一性。
所有类都继承于Object。Object的hashCode方法生成的散列码,实际上是默认使用对象的地址计算散列码;而Object的equals方法实际上就是地址比较(==)。显然,当我们在使用散列容器(如HashMap的Key,HashSet等)时,我们自定义的类中还继承Object的hashCode和equals是不行的。必须重写hashCode和equals方法。好的hashCode()应该产生分布均匀的散列码。可以用IDE自动生成。下面是一个例子:
1 import java.util.List; 2 3 public class Test9 { 4 5 boolean a; 6 byte b; 7 short c; 8 int d; 9 char e; 10 long f; 11 float g; 12 double h; 13 String i; 14 List<String> j; 15 int[] k; 16 17 @Override 18 public int hashCode() { 19 // [STEP1] hashCode()里的魔法数字,之所以选择31,是因为它是个奇素数,如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算。 20 // 使用素数的好处并不是很明显,但是习惯上都使用素数来计算散列结果。31有个很好的特性,就是用移位和减法来代替乘法,可以得到更好的性能:31*i==(i<<5)-i。现在的VM可以自动完成这种优化。(from 《Effective Java》) 21 final int prime = 31; 22 // [STEP2] 为对象中每个有意义的域用下面公式计算散列码 result = prime * result + c 23 int result = 1; 24 // boolean 25 result = prime * result + (a ? 1231 : 1237); 26 // byte/short/int/char 27 result = prime * result + b; 28 result = prime * result + c; 29 result = prime * result + d; 30 result = prime * result + e; 31 // long 32 result = prime * result + (int) (f ^ (f >>> 32)); 33 // float 34 result = prime * result + Float.floatToIntBits(g); 35 // double 36 long temp; 37 temp = Double.doubleToLongBits(h); 38 result = prime * result + (int) (temp ^ (temp >>> 32)); 39 // 对象 40 result = prime * result + ((i == null) ? 0 : i.hashCode()); 41 // List(要求每个元素实现hashCode) 42 result = prime * result + ((j == null) ? 0 : j.hashCode()); 43 // 数组(要求每个元素实现hashCode) 44 result = prime * result + Arrays.hashCode(k); 45 // [STEP3] 返回 46 return result; 47 } 48 @Override 49 public boolean equals(Object obj) { 50 if (this == obj) 51 return true; 52 if (obj == null) 53 return false; 54 if (getClass() != obj.getClass()) 55 return false; 56 Test9 other = (Test9) obj; 57 if (a != other.a) 58 return false; 59 if (b != other.b) 60 return false; 61 if (c != other.c) 62 return false; 63 if (d != other.d) 64 return false; 65 if (e != other.e) 66 return false; 67 if (f != other.f) 68 return false; 69 if (Float.floatToIntBits(g) != Float.floatToIntBits(other.g)) 70 return false; 71 if (Double.doubleToLongBits(h) != Double.doubleToLongBits(other.h)) 72 return false; 73 if (i == null) { 74 if (other.i != null) 75 return false; 76 } else if (!i.equals(other.i)) 77 return false; 78 if (j == null) { 79 if (other.j != null) 80 return false; 81 } else if (!j.equals(other.j)) 82 return false; 83 if (!Arrays.equals(k, other.k)) 84 return false; 85 return true; 86 } 87 }