【原创】关于hashcode和equals的不同实现对HashMap和HashSet集合类的影响的探究
这篇文章做了一个很好的测试:http://blog.csdn.net/afgasdg/article/details/6889383,判断往HashSet(不允许元素重复)里塞对象时,是如何判定set里的对象是否一致的问题。但是他是从写测试例子入手来看的,或许全文看下来还迷迷蒙蒙,印象不够深刻。
所以看了这篇博文,我又去看了下HashSet的源码,在添加新元素时,调用的方法如下:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
这说明,HashSet里存储对象最终还是利用了HashMap来存,所以如何判定对象是否一致的问题还要落在HashMap上。看看HashMap的put方法实现:
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
} modCount++;
addEntry(hash, key, value, i);
return null;
}
其中最关键的几行,
int hash = hash(key);
取得传入的key的hash值,当然这里的key就是我们需要存储如HashSet的对象啦。
那是如何取得hash值的呢?看看hash方法是如何实现的:
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
} h ^= k.hashCode(); // This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
对于非String对象,采取的是调用key自身实现的hashcode方法,并做一些异或操作,减少冲突,使得存储到HashMap时相对分散,提高效率。
返回到put方法上的第二个关键代码行:
int i = indexFor(hash, table.length);
这一行代码是取得hash值对应的index位置,取得之后,就开始在对应链表上查找对象了(顺便说下HashMap的存储结果,它是一个数组和链表的组合体,由hashcode定位到数组的对应位置,然后将对象存储到这个位置指向的链表中,http://www.cnblogs.com/highriver/archive/2011/08/15/2139462.html文章说的很清楚,可以参考阅读)。在链表中查找对象的代码如下:
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
那么重点来了,看这一行:
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
所以这里我们可以得出结论:在往HashSet里add对象或者往HashMap里add对象(看key是否相同)时,比较对象(HashMap中是对其key比较)是否相同时,先看hashcode是否一样,不一样则表示一定不一样,可以添加;否则就表示是不相同的key(对hashset来说就是不同对象,可以同时保存在set中);如果hashcode相同,再比较对象地址是否一样或者两个对象是否equals,有一个为true则表示是相同的。
相同则更新value值,这个我们不必关心,因为hashset没有value值,或者说value值都是PRESENT,而PRESENT是一个东东:
private static final Object PRESENT = new Object();
看到这里,差不多明白了HashSet是如何比较存入对象是否相同的了。那又该反思下自定义的覆盖的hashcode方法和equals方法实现了。
要使用HashSet和HashMap这些集合类进行存储,必须覆盖hashcode方法和equals方法实现,不然调用的都是Object的默认实现,而Object的默认实现是这样的:
equals默认实现:
public boolean equals(Object obj) {
return (this == obj);
}
这里实现默认是对比对象的物理地址,如果你要改变这种equal方式,需要自己覆盖重写。看HashMap中对比一个key是否存在中&&后面的
(k = e.key) == key || key.equals(k)
只要一个成立则可以,所以equals的默认实现再这里就没有意义了,其表达的内在含义就在于:只要你覆盖了Object的equals默认实现,就只看你自己的equals 来绝对是否相等了(因为一般物理地址相等的不可能不equals)。
再来看看hashcode的实现:
/**
* 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}.
* <p>
* The general contract of {@code hashCode} is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the {@code hashCode} method
* must consistently return the same integer, provided no information
* used in {@code equals} comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
* <li>If two objects are equal according to the {@code equals(Object)}
* method, then calling the {@code hashCode} method on each of
* the two objects must produce the same integer result.
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link java.lang.Object#equals(java.lang.Object)}
* method, then calling the {@code hashCode} method on each of the
* two objects must produce distinct integer results. However, the
* programmer should be aware that producing distinct integer results
* for unequal objects may improve the performance of hash tables.
* </ul>
* <p>
* As much as is reasonably practical, the hashCode method defined by
* class {@code Object} does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the
* Java<font size="-2"><sup>TM</sup></font> programming language.)
*
* @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();
也许你看到会很奇怪,为啥没有实现呢?都是祖先类了。这也是我为什么贴注释的原因了。
native关键字所定义的方法的实现不在java源码范围内,它是java与其他语言协作运行所定义的一种方法。
它是操作系统基本的实现,一般应该是用C/C++实现的的原生方法,并且被编译为DLL,由Java去调用。我们知道Java是一个跨平台的语言,所以它只能牺牲这些底层的控制,根据不同平台的具体实现来调用。
【原创】关于hashcode和equals的不同实现对HashMap和HashSet集合类的影响的探究的更多相关文章
- 实现对HashMap的value排序
问题:如何对HashMap中的value值进行排序 关键点:1.取HashMap的Map.Entry,放入List2.利用Collections.sort(List, Comparator<? ...
- hashCode与equals详解
在工作中写业务类通常都会重写hashCode与equals方法,而这两个方法的区别与用途也常常被问道.平时也只是大概知道这二者的用途,今天闲下来,查阅资料加上自己的理 解,总结记录下. hashCod ...
- 【JAVA】hashcode() & equals()
平时使用map时都是用JAVA原生的类型,所以很少关注到hashcode()和equals()的方法的内部实现.近期实现一个小工具,涉及到自己写的类的查找比对,又再次重温了相关的知识. 上简单示例代码 ...
- HashMap的工作原理-hashcode和equals原理的再次深入
前言 首先再次强调hashcode (==)和equals的真正含义(我记得以前有人会说,equals是判断对象内容,hashcode是判断是否相等之类): equals:是否同一个对象实例.注意,是 ...
- [转载] HashMap的工作原理-hashcode和equals的区别
目录 前言 为什么需要使用Hashcode,可以从Java集合的常用需求来描述: 更深入的介绍 先来些简单的问题 HashMap的0.75负载因子 总结 我在网上看到的这篇文章,介绍的很不错,但是我看 ...
- hashCode和equals的区别
关注公众号,大家可以在公众号后台回复“博客园”,免费获得作者 Java 知识体系/面试必看资料. 有面试官会问:你重写过 hashcode 和 equals 么,为什么重写equals时必须重写has ...
- 关于hashcode和equals方法说明
一.前言 我们都知道,要比较两个对象是否相等时需要调用对象的equals()方法,即判断对象引用所指向的对象地址是否相等,对象地址相等时,那么与对象相关的对象句柄.对象头.对象实例数据.对象类型数据等 ...
- 重写hashcode和equals方法
重写hashcode和equals方法 简乐君 2019-05-07 21:55:43 35481 收藏 191分类专栏: Java 文章标签: equals() hashcode()版权 一.前言我 ...
- 对hashcode、equals的理解
1.首先hashcode和equals都是java每个对象都存在的方法,因为他们两是Object的方法. 2.hashcode方法默认返回的是该对象内存地址的哈希码,然而你会发现,Object类中没有 ...
随机推荐
- thinkphp 框架的学习(1) 扩展配置文件
在config.php里面写入 1:'LOAD_EXT_CONFIG' => array('SETTINGS' => 'settings'); 系统会判断是否有参数:LOAD_EXT_CO ...
- Python和VS
下载VS Code 安装插件Python 安装Python,注意这里需要把Python的目录配置到环境变量中 文档结构非常重要,py文件一定位于根目录,.vscode平级:我曾经因为py文件在.vsc ...
- qt 5 小练习 创建无边框界面
我们大家都知道QT5 自带的界面不是那么美观,并且每个软件我们都发现他们的边框是自定义的,所以我决定写一篇这样的博文,也许已经有许许多多篇大牛写的论文了,但我还是想写一篇记录自己的学习QT的历程 首先 ...
- Git权威指南 读笔(3)
第九章 恢复进度: $ git stash list 显示存储的工作进度列表. $ git stash 保存当前的工作进度,分别对暂存区和工作区的状态进行保存. $ git stash pop [-- ...
- 常用的四种CSS样式表格
1. 单像素边框CSS表格 这是一个很常用的表格样式. [html] <style type="text/css"> table.gridtable { font-fa ...
- What Can I Do With This Major?
What Can I Do With This Major? Majors Don’t see your major? Accounting Advertising Africana Studies ...
- PHP漏洞全解(八)-HTTP响应拆分
本文主要介绍针对PHP网站HTTP响应拆分,站在攻击者的角度,为你演示HTTP响应拆分. HTTP请求的格式 1)请求信息:例如“Get /index.php HTTP/1.1”,请求index.ph ...
- android 设备唯一码的获取,Cpu号,Mac地址
开发Android应用中,我们常常需要设备的唯一码来确定客户端. Android 中的几中方法,使用中常常不可靠 1. DEVICE_ID 假设我们确实需要用到真实设备的标识,可能就需要用到DEVIC ...
- Android-加载透明PNG图片变黑的问题
png和jpg作为两种最常用的图片格式,首先我们要知道他们的区别: 1.从一般图片的外观上来说,他们是无法直接判断的 2.从文件大小上来说,同样一张图png肯定比jpg的大 3.通过查资料咱们可以发现 ...
- module_init宏解析
在init.h中我们看到 #define module_init(x) __initcall(x); 还看到 #define __initcall(fn) device_initcall(fn) 还有 ...