哈希表(散列表)
k-v形式,将k通过hash函数处理生成 table中的索引
添加、搜索、删除的流程都是类似的
利用哈希函数生成 key 对应的 index 【O(1)】
根据 index 操作定位数组元素 【O(1)】
(空间换时间的典型应用)
-
哈希表是【空间换时间】的典型应用
-
从哈希函数,也叫做散列函数
-
哈希表内部的数组元素,很多地方也叫 Bucket(桶),整个数组叫 Buckets 或者 Bucket Array
hash冲突(hash碰撞)
两个不同key通过hash函数计算出相同hash值
解决hash冲突常见方法:
-
开放地址法
-
按照一定规则向其他地址探测,直到遇到空桶
-
-
再hash法
-
设计多个hash函数,若冲突则通过其他hash函数再计算hash值
-
-
链地址法
-
通过链表将同一index的元素串起来
-
jdk8使用单向链表加红黑树解决hash冲突
-
默认使用单向链表将元素串起来
-
在添加元素时,可能会由单向链表转为红黑树来存储元素
-
比如当哈希表容量 ≥ 64 且 单向链表的节点数量大于 8 时
-
当红黑树节点数量少到一定程度时,又会转为单向链表
-
-
JDK1.8中的哈希表是使用链表+红黑树解决哈希冲突
思考:这里为什么使用单链表?
-
每次都是从头节点开始遍历
-
单向链表比双向链表少一个指针,可以节省内存空间
hash函数
为了提高效率,可以使用 & 位运算取代 % 运算【前提:将数组的长度设计为 2 的幂(2n)】
floatToIntBits: 将浮点数底层对应的二进制数转换为整数
toBinaryString:将整数转换为对应的二进制数
Integer.toBinaryString(Float.floatToIntBits(10.6f))
java底层hash值计算方式
整数
整数值当做哈希值
比如 10的哈希值就是 10
public static int hashCode(int value) {
return value;
}
浮点数
将存储的二进制格式转为整数值
public static int hashCode(float value) {
return floatToIntBits(value);
}
Long和Double
无符号右移 >>>
右移 >>
异或 ^ :相同为0,不同为1
">>>"和"^" 的作用是? 高32bit 和 低32bit 混合计算出 32bit 的哈希值 充分利用所有信息计算出哈希值
(int)(bits ^ (bits >>> 32))
在强制类型装换时,底层会直接将高32位舍弃掉,最后的hash值就是
对应的整数值
字符串
字符串是由若干个字符组成的
比如字符串 jack,由j、a、c、k四个字符组成(字符的本质就是一个整数)
因此,jack 的哈希值可以表示为j *n^3 + a * n^2 + c * n^1 + k * n^0,等价于[(j *n + a) *n+ c] *n+ k
在JDK中,乘数 n 为 31,为什么使用 31?
-
31 是一个奇素数,JVM会将 31 *i 优化成(i<<5) - i
//自己实现:
public static void main(String[] args) {
String string = "jack";
int len = string.length();
int hashCode = 0;
for (int i = 0; i < len; i++) {
char c = string.charAt(i);
hashCode = hashCode * 31 + c;
// hashCode = (hashCode << 5) - hashCode + c;
System.out.println(hashCode);
// hashCode = ((j* 31 + a)* 31 + c) * 31 + k
}
}
//jdk底层 java官方实现:
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;
}
自定义对象
自定义对象的hash值是根据内存地址生成,所以对于每一次new产生的对象,在java中,若不重写hashCode方法,由于new的对象地址不同,则生成的hash值也不同。
若认为当自定义对象new出来的只要属性值相同,就认为是同一对象时,则就不能使用java默认的使用内存地址生成的hash值(new出来对象内存地址不同),而应该重写hashCode方法根据对象的属性值重新计算hashCode的值。
存入hash表时若hash冲突
存入hash表时若hash冲突,先通过计算出hash值计算出hash表的索引,若索引冲突,
-
对象:则通过equals(重写equals,不重写又是比较的内存地址)判断对象是否相等(key是否相等)
-
其他类型: 直接判断key是否相同
重写hashCode与equals
hashCode调用: 计算hash的索引时调用
equals调用: hash冲突时,计算key是否相同调用
额外知识点复习:
getClass与 instanceof:
instanceof 用于判断类或者接口的类型时,若是该类的子类,或者接口的实现类,也会认为是同一类型,返回ture
getClass只会判断是不是和该类是同一类型,不会判断其子类和实现类
在计算对象hash值时n为什么选择31:
31不仅仅是符合2^n-1,它是个奇素数(既是奇数,又是素数,也就是质数)
-
素数和其他数相乘的结果比其他方式更容易产成唯一性,减少哈希冲突最终
-
选择31是经过观测分布结果后的选择
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!