摸鱼吹牛逼——聊聊哈希表

371 阅读4分钟

何谓哈希表

哈希函数

哈希表中哈希函数的实现步骤大致如下:

  1. 先生成key的哈希值(必须是整数)
  2. 再让key的哈希值跟数组的大小进行相关运算。 常见如下:
public int hash(Object key){
    return hash_code(key) % table.length;
}

对key进行哈希,然后再对数组的长度进行模运算。 但是通常,为了提高效率,可以使用&位运算取代%运算。而前提是要将数组的长度设计为2的幂。

public int hash(Object key){
    return hash_code(key) & (table.length-1);
}

如何生成key的哈希值

key的常见种类可能有字符串、整数、浮点数或者自定义对象。 不同种类的key的哈希值的生成方式是不一样的,但是他们的目标是一致的。

  • 要尽量让每个key的哈希值是唯一的
  • 尽量让key的所有信息都参与到运算中去。

而在Java中,HashMap的key必须实现hashCode、equals方法,也可以让key为null。Java里面的哈希值必须为int类型。

int整数

整数值就直接使用整数其本身作为哈希值。

float浮点数

浮点数 在 内存中也是二进制。那么就直接把其二进制的表示形式转化为整数。 在Java中可以使用 Float.floatToIntBits() 来将浮点数转化为对应的整数。然后再调用Integer.toBinaryString()即可将其转化为对应的二进制形式。

如: 对于浮点数10.6,可以有:

int num = Float.floatToIntBits(10.6f);
System.out.println("10.6f对应的整数是" + num);
String binary = Integer.toBinaryString(num);
System.out.println("10.6f对应的整数转化后的二进制是" + binary);

输出结果:

10.6f对应的整数是1093245338
10.6f对应的整数转化后的二进制是1000001001010011001100110011010

所以浮点数10.6f的哈希值就是其对应的整数是1093245338。

Long类型的哈希值

对于Long类型,Java的官方的算法如下: Long是8个字节,64位的。

public static int hashCode(long value) {
    return (int)(value ^ (value >>> 32));
}

意思是先让value值无符号右移32位,然后再和value值本身异或。这样得出来的便是Long的哈希值。 其中的^和>>>的作用是什么?其作用在于让高32bit 和 低32bit 混合计算出32bit的哈希值,从而充分利用所有信息计算出哈希值。

^ 异或,相同为0,不同为1。 如1|1=0 , 1|0=1 , 0|0=0;

image.png 如图所示,最后我们得到的哈希值是橙色部分的二进制转化为的整数。

Double类型的哈希值

对于Double类型的哈希值算法如下: Double类型同Long类型,也是8字节64位的,因此其求哈希值的方式与Long大同小异。 但是Double多了一步doubleToLongBits,用于将Double的小数转化为相对应的Long类型。 这一步和float有相似,都要先从对应的小数转化为对应的整数或长整数。

public static int hashCode(double value) {
    long bits = doubleToLongBits(value);
    return (int)(bits ^ (bits >>> 32));
}

字符串的哈希值

字符串的哈希值与前面的都有所不同。

我们可以通过类比来说明下。 比如,对于一个数字 8536,其实我们可以将其表示成: 8 × 10^3 + 5 × 10^2 + 3 × 10^1 + 6 × 10^0

那么同样的,对于一个字符串,我们也可以通过类似的方法来将其表示。 比如对于“Sitr”,可以表示成: S × n^3 + i × n^2 + t ×n^1 + r × n^0

这里的n表示字符的进制。 然后我们再把n提取公因数: n ×( n × (S × n + i ) + t) + r 也即: [(S × n + i) × n + t] × n + r

那么这里的n如何决定呢? 在JDK的字符串的HashCode里面,是将n视为31。 这是因为31是一个奇素数,进行哈希的时候更容易得到一个唯一值。而且在JDK里面,31 × i还可以进行优化,优化成 (i << 5) - i。

注1:此时i表示字符串的某个字符的对应的ANSII数。 注2:31 × i转化的推导 31 × i = (2^5 - 1) × i = i × 2^5-i = (i<<5) - i

见JDK中的字符串的HashCode:

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}