网站首页 > 技术文章 正文
首先我们看下Object类的部分源码,可以得到如下结论:
- 它是Object对象的一个方法,调用的本地方法(不同JVM实现不同)
- 通过该方法可以利用哈希表的一些优点,例如HashMap(方法描述翻译过来的大致意思)
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.
* ....
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
public native int hashCode();
那么如何使用这个方法呢,如何去利用哈希表的优点呢?
我们可以看下JDK的Integer类和String类是如何实现的:
Integer的hashCode和equals:
@Override
public int hashCode() {
return Integer.hashCode(value);
}
public static int hashCode(int value) {
return value;
}
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
- Integer.hashCode和equals方法一样,都是通过内部的int值来实现的
- 不同int值的Integer对象hashCode不同,equals返回false;相同int值的Integer对象hashCode相同,equals返回true。
所以,Integer的int值具有天然的防止哈希碰撞的属性,这也是前几篇文章我们用Integer举例的原因。
String的hashCode和equals
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;
}
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
- String.hashCode和equals方法一样,都是通过内部的char数组来实现的
- String.hashCode的公式可以理解为char[0]*31^(n-1) + char[1]*31^(n-2) + ... + char[n-1]
为什么要使用h = 31 * h + val[i];这样的公式来产生hashcode呢?
答:这是一个多项式哈希,目的是为了将char数组的位置信息带入到hashcode的产生逻辑中。如果我们写成h = h + val[i]的话,字符串"abc"和"cba"将产生相同的hashcode,这样就发生哈希冲突了。
为什么String要用31作为乘数呢?
JDK-4045622(链接请见文章尾部)里面提到,当时针对三个数据源(横轴)和几种比较知名的哈希方法(纵轴)进行了统计,P(31)是其中效率比较高的之一(数值越小效率越高),同时因为31在RISC机器上计算效率更高(2的指数倍-2,2的指数倍相当于左移操作)。
Webster's Code Strings URLs
--------- ------------ ----
Current Java Fn. 1.2509 1.2738 13.2560
P(37) [Java] 1.2508 1.2481 1.2454
P(65599) [Aho et al] 1.2490 1.2510 1.2450
P(31) [K+R] 1.2500 1.2488 1.2425
P(33) [Torek] 1.2500 1.2500 1.2453
Vo's Fn 1.2487 1.2471 1.2462
WAIS Fn 1.2497 1.2519 1.2452
Weinberger's Fn(MatPak) 6.5169 7.2142 30.6864
Weinberger's Fn(24) 1.3222 1.2791 1.9732
Weinberger's Fn(28) 1.2530 1.2506 1.2439
如何使用hashCode方法呢?
我们同样看下HashMap里面是如何使用的:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
...
这里首先比较hash值是否一样(p.hash == hash),然后判断对象是否相等((k = p.key) == key || (key != null && key.equals(k)))。
总结
- 如果我们想将java对象使用哈希表的相关优点(插入/查找的复杂度为常量),我们需要重写hashCode和equals方法。
- hashCode方法的逻辑也很重要,会影响到程序的性能。
- 如何写好hashCode方法可以参考现有的JDK类的实现,它们背后都是经过了大量的实践和理论论证的。
相关链接:
JDK-4045622 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045622
其他相关文章:
猜你喜欢
- 2025-10-02 别让 uint8 毁了你的字符串:C++ 中uint8转字符串指南
- 2025-10-02 面试懵了:StringBuilder 为什么线程不安全?
- 2025-10-02 源码分享:在pdf上加盖电子签章_pdf添加电子印章的格式
- 2025-10-02 为什么?为什么StringBuilder是线程不安全的?
- 2025-10-02 Java25的新特性_java特性有哪些
- 2025-10-02 【C语言·025】字符数组与字符串字面量的存储区别
- 2025-10-02 String、StringBuilder、StringBuffer源码分析
- 2024-08-08 Rust 与 C 之间,传递字符串的 7 种方式
- 2024-08-08 C语言将十六进制数据转换为字符串
- 2024-08-08 C语言快速入门——字符串生成(c语言程序设计字符串)
- 10-02基于深度学习的铸件缺陷检测_如何控制和检测铸件缺陷?有缺陷铸件如何处置?
- 10-02Linux Mint 22.1 Cinnamon Edition 搭建深度学习环境
- 10-02AWD-LSTM语言模型是如何实现的_lstm语言模型
- 10-02NVIDIA Jetson Nano 2GB 系列文章(53):TAO模型训练工具简介
- 10-02使用ONNX和Torchscript加快推理速度的测试
- 10-02tensorflow GPU环境安装踩坑日记_tensorflow配置gpu环境
- 10-02Keye-VL-1.5-8B 快手 Keye-VL— 腾讯云两卡 32GB GPU保姆级部署指南
- 10-02Gateway_gateways
- 最近发表
-
- 基于深度学习的铸件缺陷检测_如何控制和检测铸件缺陷?有缺陷铸件如何处置?
- Linux Mint 22.1 Cinnamon Edition 搭建深度学习环境
- AWD-LSTM语言模型是如何实现的_lstm语言模型
- NVIDIA Jetson Nano 2GB 系列文章(53):TAO模型训练工具简介
- 使用ONNX和Torchscript加快推理速度的测试
- tensorflow GPU环境安装踩坑日记_tensorflow配置gpu环境
- Keye-VL-1.5-8B 快手 Keye-VL— 腾讯云两卡 32GB GPU保姆级部署指南
- Gateway_gateways
- Coze开源本地部署教程_开源canopen
- 扣子开源本地部署教程 丨Coze智能体小白喂饭级指南
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (76)
- js判断是否是json字符串 (75)
- c语言min函数头文件 (77)
- asynccallback (87)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 无效的列索引 (74)