文章目录

  • 1、哈希表简单的原理理解
  • 2、哈希冲突
  • 3、哈希冲突解决办法
  • 3.1 Java 里面怎么解决哈希冲突?
  • 3.2 Java 为什么使用单链表?链表 + 红黑树?为什么使用?
  • 4、 哈希函数的作用
  • 5、哈希函数实现的大致步骤
  • 6、相关运算符号(哈希函数中会遇到)
  • 6.1、关于 & 运算
  • 6.2 关于 ^ 运算(异或运算,相同的为 0 ,不同的 1 )
  • 6.3 >>> 表示的是无符号右移动,就是二进制的位向右移动溢出的直接截取掉即可
  • 7、不同的 key 产生的hashCode 的过程
  • 7.1 整数
  • 7.2 浮点数(计算机组成原理有怎么把浮点数转换为二进制)
  • 7.3 long double(都是64位,使用位,异或,类型转换变为 32位的int)
  • 7.4 字符串的哈希值计算
  • 8、Java里面不重写 hashCode 的后果
  • 9、为什么Map里面的 key 要求实现 equals
  • 10、小结(为什么自定义类型需要同时重写hashCode和 equals方法)
  • 11 obj.getClass() 和 obj instance()的区别
  • 12 字符串哈希值的计算为什么使用 31(简单的数学原理)
  • 使用了自定义对象作为 key

1、哈希表简单的原理理解

通过映射的键值对,将 key 进行传递进去,然后经过了哈希函数的处理,计算得到数组中的下标,使得进行索引的的时间复杂度是 O(1)
通过了空间换时间;牺牲了空间,快速了时间

2、哈希冲突

两个不同的key 但是最终的计算结果是相同的,也就是计算到了相同的数组下标,在元素的插入的时候,发生了冲突,出现了哈希碰撞;

3、哈希冲突解决办法

1、开放寻址法,进行空桶的探测,顺序的探测,跳转的探测;
2、再哈希方法:另外的哈希算法实现;
3、使用链表将桶子里面的值进行串起来;

3.1 Java 里面怎么解决哈希冲突?

Java 里面使用的单向链表解决哈希冲突,当哈希表的容量 >= 64 并且单项链表的节点数量 > 8 的时候,此时会把哈希表转换为红黑树,
当红黑书的节点小于一定的数量时候,转换为单向链表;

3.2 Java 为什么使用单链表?链表 + 红黑树?为什么使用?

使用单链表的原因:因为当元素的插入的时候,发生了哈希冲突,会使用链表 + 红黑树的存储方式,先在链表上会进行元素的 key 的比较,相同的 key 存在的时候,覆盖掉原来的key 的 value,没有相同的 key ,追加到链表的尾部即可;

4、 哈希函数的作用

通过哈希函数将 key 传递进去,计算出来数组的下标,就是存储元素,键值对的映射;

5、哈希函数实现的大致步骤

哈希函数实现的大致步骤:
1、使用key 生成哈希值(哈希值必须是使用整数)
2、使用 key 生成的哈希值与数组的大小进行相关运算,生成一个索引值(因为最终数据是放在了数组元素中的,计算出来的哈希值是需要
小于数组的索引的)
比如使用下面的计算方式:
hashCode(key) % arr.length
使用与运算(&) 取代 % (计算的速度比较慢,选择更加好的计算方式)运算的条件:数组的长度是 2^n

6、相关运算符号(哈希函数中会遇到)

6.1、关于 & 运算

& 运算:


   1001010
&  1101101
rs 1001000(两个都是 1 的时候结果才是 1)


为什么数组的长度是 2^n?为了提高计算机的运行效率;
     1 2^0
     10 2^1
     100 2^2
     1       2^1 - 1
     11      2^2 - 1
     111     2^3 - 1
     1111    2^4 - 1


      1001010
    & 1111111
   rs 1001010(是原来的key)(以前是什么,现在是什么)
     


     1010101(不管之前的数字有多大,算出来的结果始终是小于 给定的数组大小 111 的)
   & 0000111
  rs 0000101

6.2 关于 ^ 运算(异或运算,相同的为 0 ,不同的 1 )

6.3 >>> 表示的是无符号右移动,就是二进制的位向右移动溢出的直接截取掉即可

JAVA中的哈希值是什么意思_数组


上面的表中,先进行了移位的操作,然后进行了 异或的运算,使得高 32 位,低32 位都参加了哈希值的运算,减少了哈希冲突;

为什么使用 ^ 运算?
因为如果使用了 & 的运算,方面的前面 32 位相当于没有参加运算,不符合所有的 key 都参加hashCode 运算的设计规则

为什么不使用 | 运算?
因为算出来的哈希冲突太大了,不符合设计规则

7、不同的 key 产生的hashCode 的过程

7.1 整数

整数自己就是自己的哈希值

7.2 浮点数(计算机组成原理有怎么把浮点数转换为二进制)

使用浮点数在计算机内部表示的二进制转换成为的 int 整数作为它的哈希值;

7.3 long double(都是64位,使用位,异或,类型转换变为 32位的int)

下面是JDK Double 类中 hashCode 的计算方法,先进行了位运算,然后进行了异或运算,然后把 64 位的 double 强制的进行了 32 位的int 的类型转换,得到了规定的哈希值;
因为 Java 里面规定了哈希值是32 位的整数,为了获取到这个整数,进行了上面的操作,计算,类型转换

/**
     * Returns a hash code for a {@code double} value; compatible with
     * {@code Double.hashCode()}.
     *
     * @param value the value to hash
     * @return a hash code value for a {@code double} value.
     * @since 1.8
     */
    public static int hashCode(double value) {
        long bits = doubleToLongBits(value);
        return (int)(bits ^ (bits >>> 32));
    }

7.4 字符串的哈希值计算

字符串是由字符所组成的,每一个字符在本质上面就是一个数字,我们可以使用 31 进制将字符转换成为数字,自己定义,转换成了一个整数,可以作为hash Code ,hashCode 经过了不同的算法可以得到不同的结果,最终的目标是,提高检索的效率,减少哈希冲突;

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;
    }

原因:31的优点是可以用移位和减法来代替乘法,以获得更好的性能:31 * i == (i << 5) - i…现代VM自动进行这种优化。

8、Java里面不重写 hashCode 的后果

在使用了 HashMap 存储对象的时候,不重写,JDK 根据对象创建的内存地址自动的生成 hashCode,只是简单比较了两个对象的地址不同,那么hashCode 就不会相同,但是在实际的开发中,要求当两个对象的某几个属性值相同的时候,需要判定两个对象就是一个对象,这个时候把对象保存在HashMap 中就必须重写 hashCode 方法,否则两个对象永远不会相同;允许 key 是 null

9、为什么Map里面的 key 要求实现 equals

能放在一起不代表哈希值是一样的,代表哈希值算出来的索引是一样的;
不能使用哈希值相等就说两个东西是相等的,因为不同的int string 创建出来的变量的哈希值有可能是一样的;
所以需要使用 equals 方法进行进一步的判断,进一步的判断 key 是不是相等的,key 相等了,覆盖,不相等,后面查找,一直没有放在单向链表的最后面;

10、小结(为什么自定义类型需要同时重写hashCode和 equals方法)

把一个 value 放到哈希表中需要知道住的位置,hashCode相当于住的街道,equals可以找到住的房间号;

1、为什么需要hashCode?
因为创建哈希表,在Java中哈希表由数组链表组成(简单理解,有红黑树转换),把一个 value 放在一个哈希表,得先知道放到哪儿,key 计算得到hashCode , hashCode经过了计算之后得到数组的下标(可能是多个不同的东西计算得到的哈希值是一样的,哈希值的唯一确定不能确定就是同一个对象);
解决了数组中的什么位置;解决了住在什么街道的问题;

2、为什么需要 equals?
放入元素的时候,数组中的每一个元素,可以看做是一个单向链表中头结点,当发生了哈希冲突,比较哈希表已有元素的 key 和新加入的元素的 key 是不是相等的,相等的key 进行值的覆盖,不相等一直向链表后面遍历,如果一直没有相同的key,那么加到单向链表最后面;
解决了住在什么房间号的位置;

(key 可以为数值,比较数值的大小,就是比较了key 是否是相等的;可以为对象,key 是对象的时候,我们可以根据对象的属性重写 equals,根据属性判断两个对象是不是一个对象,也就是比较key 是不是相等的)

11 obj.getClass() 和 obj instance()的区别

obj.getClasss() 获取到的就是当前的类
obj instance() 当其为 子类的时候,判断出来的是 true 范围变大了,变得不精确了

12 字符串哈希值的计算为什么使用 31(简单的数学原理)

JAVA中的哈希值是什么意思_哈希冲突_02


使用了31的优点:

素数与其他数字相乘,更容易产生唯一性,减少了哈希冲突;

31计算出来的哈希值是分布比较均匀的,经过了某些实验之后;

使用了自定义对象作为 key

1、需要充实重写 hashCode() equals() 方法
2、equals()判断两个 key 是否是同一个 key
3、当equals()方法返回的是 TRUE ,他们之前的哈希值一定是一样的
4、hashCode 相等的key equals() 不一定是TRUE