哈希表以及Java里面的哈希表
文章目录
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 >>> 表示的是无符号右移动,就是二进制的位向右移动溢出的直接截取掉即可
上面的表中,先进行了移位的操作,然后进行了 异或的运算,使得高 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(简单的数学原理)
使用了31的优点:
素数与其他数字相乘,更容易产生唯一性,减少了哈希冲突;
31计算出来的哈希值是分布比较均匀的,经过了某些实验之后;
使用了自定义对象作为 key
1、需要充实重写 hashCode() equals() 方法
2、equals()判断两个 key 是否是同一个 key
3、当equals()方法返回的是 TRUE ,他们之前的哈希值一定是一样的
4、hashCode 相等的key equals() 不一定是TRUE
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!