Java数据结构_哈希表_基本概念

2021-04-20  本文已影响0人  信仰年輕

本文目标

哈希表的基本概念,哈希冲突,哈希函数

什么是哈希表

哈希表也叫做散列表(hash有剁碎的意思)
哈希表是空间换时间的典型应用
哈希表内部的数组元素,很多地方也叫做Bucket(桶),整个数组叫Buckets或者 Bucket Array

哈希冲突

哈希冲突也叫做哈希碰撞
2个不同的key,经过哈希函数计算得到相同的结果

JDK1.8的哈希冲突解决方案

默认采用采用单向链表将元素穿起来
在添加元素的时候也可以能由单向链表转成红黑树,比如当哈希表容量≥64 且单向链表的节点数量>8
当红黑树节点数量少到一定程度时候,又会转为单向链表

为什么使用单链表?

1.每次都是从头结点开始遍历
2.单向链表比双向链表少一个指针,节省内存空间

哈希函数

哈希表中哈希函数的实现步骤大概如下
1.先生成key的哈希值(必须是整数)
2.在让key的哈希值和数组的大小进行相关运算,生成一个索引值

public int  hash(Object key) {
    return hashCode(key) % table.length;
}

为了提高效率,可以使用&位运算取代%运算【前提:将数组的长度设计为2的幂(2^n)】

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

为什么可以使用&位运算取代%运算
二进制 2^n

10                 2^1
100                2^2
1000               2^3
10000              2^4

二进制 2^n - 1

01                 2^1-1
011                2^2-1
0111               2^3-1
01111              2^4-1

不难发现,都是2^n - 1 转成二进制都是一排1(最前面的0可以忽略)

假如说现在hash_code(key)为10101,然后与数组长度为(2^3 - 1)做&运算

  10101    哈希值
& 00111    数组的长度
---------------
  00101    索引值

得出来的结果 00101必然是小于00111的(数组的长度2^3 - 1)

良好的哈希函数
让哈希值更加均匀分布->减少哈希冲突此时->提升哈希表的性能

如何生成key的哈希值

key的常见种类可能有
1.整数,浮点数,字符串,自定义对象
2.不同种类的key,哈希值的生成方式不一样,但目标是一致的
尽量让每个key的哈希值是唯一的
进来让key的所有信息参与运算

1.整数的哈希值

把整数值当做哈希值
比如10的哈希值就是10

public static int  hashCode(int value) {
    return value;
}

2.浮点数的哈希值

将存储的二进制个数转为整数值

public static int  hashCode(float value) {
    return Float.floatToIntBits(value);
}

3.Long的哈希值

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

4.Doble的哈希值

public static int  hashCode(double value) {
      //long类型的64位的二进制数据
     long bits = Double.doubleToLongBits(value);
     //进行低32位和高32位的 异或运算(相同为0,不同为1),算出哈希值
     return (int)(bits ^ (bits >>> 32));
}

>>> 和 ^ 的作用是?

1.高32bit和低32bit混合计算出32bit的哈希值
2.充分利用所有信息计算出哈希值


上图,value 和 value>>>32 (右移32位) 做异或^运算
此时,是value的低32位和高32位做异或^运算

|或运算,有一个为1就为1
所以不能用或运算,只能用异或
异或运算,相同为0,不同为1

5.字符串的哈希值

整数5489是如何计算出来的?

5 * 10^3 + 4 * 10^2 + 8 * 10^1 + 9 * 10^0

字符串是由若干个字符组成的
1.比如字符串jack,由j,a,c,k四个字符组成(字符的本质就是一个整数)
2.因此,jack的哈希值可以表示为

j * n^3 + a * n^2 + c * n^1 + k * n^0

等价于

[(j * n + a) * n + c] * n + k

3.在JDK中,乘数n为31,为什么使用31?
31是一个奇素数,JVM会将31 * i 优化成 (i<<5) -1

public static int  hashCode(String value) {
        int hashCode = 0;
        int len = value.length();
        for(int i=0;i<len;i++) {
            char c = value.charAt(i);
            hashCode = 31 * hashCode +c;
        }
        return  hashCode;
}
上一篇 下一篇

猜你喜欢

热点阅读