Java集合:散列与散列码

Posted Storm_L

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java集合:散列与散列码相关的知识,希望对你有一定的参考价值。

       散列的价值在于速度。我们使用数组来保存键的信息,这个信息并不是键本身,而是通过键对象生成一个数字(散列码),作为数组下标。由于数组的容量是固定的,而散列容器的大小是可变的,所以不同的键可以产生相同的数组下标(散列码)。也就是说,可能会有冲突(当然也有特例,比如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 }

 

以上是关于Java集合:散列与散列码的主要内容,如果未能解决你的问题,请参考以下文章

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

深入理解容器

散列算法与散列码

初识java集合——散列表(HashTable)

对索引存储与散列存储的一些形而上的思考

Java散列和散列码的实现