这篇文章做了一个很好的测试: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集合类的影响的探究的更多相关文章

  1. 实现对HashMap的value排序

    问题:如何对HashMap中的value值进行排序 关键点:1.取HashMap的Map.Entry,放入List2.利用Collections.sort(List, Comparator<? ...

  2. hashCode与equals详解

    在工作中写业务类通常都会重写hashCode与equals方法,而这两个方法的区别与用途也常常被问道.平时也只是大概知道这二者的用途,今天闲下来,查阅资料加上自己的理 解,总结记录下. hashCod ...

  3. 【JAVA】hashcode() & equals()

    平时使用map时都是用JAVA原生的类型,所以很少关注到hashcode()和equals()的方法的内部实现.近期实现一个小工具,涉及到自己写的类的查找比对,又再次重温了相关的知识. 上简单示例代码 ...

  4. HashMap的工作原理-hashcode和equals原理的再次深入

    前言 首先再次强调hashcode (==)和equals的真正含义(我记得以前有人会说,equals是判断对象内容,hashcode是判断是否相等之类): equals:是否同一个对象实例.注意,是 ...

  5. [转载] HashMap的工作原理-hashcode和equals的区别

    目录 前言 为什么需要使用Hashcode,可以从Java集合的常用需求来描述: 更深入的介绍 先来些简单的问题 HashMap的0.75负载因子 总结 我在网上看到的这篇文章,介绍的很不错,但是我看 ...

  6. hashCode和equals的区别

    关注公众号,大家可以在公众号后台回复“博客园”,免费获得作者 Java 知识体系/面试必看资料. 有面试官会问:你重写过 hashcode 和 equals 么,为什么重写equals时必须重写has ...

  7. 关于hashcode和equals方法说明

    一.前言 我们都知道,要比较两个对象是否相等时需要调用对象的equals()方法,即判断对象引用所指向的对象地址是否相等,对象地址相等时,那么与对象相关的对象句柄.对象头.对象实例数据.对象类型数据等 ...

  8. 重写hashcode和equals方法

    重写hashcode和equals方法 简乐君 2019-05-07 21:55:43 35481 收藏 191分类专栏: Java 文章标签: equals() hashcode()版权 一.前言我 ...

  9. 对hashcode、equals的理解

    1.首先hashcode和equals都是java每个对象都存在的方法,因为他们两是Object的方法. 2.hashcode方法默认返回的是该对象内存地址的哈希码,然而你会发现,Object类中没有 ...

随机推荐

  1. Web程序发布后显示个性化图标

    采用Tomcat发布程序后,浏览器上默认显示程序的图标是Tomcat图标.如下图所示: 需要显示自己个性化的图标,比如,这里显示一个图标. 只需要如下三步设置即可. 将制作的ico图标放在程序的根目录 ...

  2. 【BZOJ】1925: [Sdoi2010]地精部落 DP+滚动数组

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1925 题意:输入一个数N(1 <= N <= 4200),问将这些数排列成折线 ...

  3. Lucene基础(一)--入门

    Lucene介绍 lucene的介绍,这里引用百度百科的介绍Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,即它不是一个完整的全文检索引 ...

  4. C#基础|值类型和引用类型以及传参问题

    为了明白什么是值类型和引用类型,先引入你两个概念.堆内存与栈内存   堆内存与栈内存   由于咱的描述能力有限,就不对其下定义了,来看看两者的作用.   共同点: 都是用来存放数据的   不同点: 堆 ...

  5. js 字符串扩展

    <script type="text/javascript" language="javascript"> String.prototype.tri ...

  6. c++ 联合体

    联合体分配的内存大小是成员变量中最大变量的大小 联合体的成员变量共享内存 小段模式(X86就是) 低位数据存在低地址单元 大端模式                     高位字节存在低地址单元

  7. Hadoop集群(第5期)_Hadoop安装配置

    1.1 Hadoop简介 Hadoop是Apache软件基金会旗下的一个开源分布式计算平台.以Hadoop分布式文件系统(HDFS,Hadoop Distributed Filesystem)和Map ...

  8. Seven Python Tools All Data Scientists Should Know How to Use

    Seven Python Tools All Data Scientists Should Know How to Use If you’re an aspiring data scientist, ...

  9. 如何使用 Xcode Targets 管理开发和生产版本?

    在开始此教程之前,我们假设你已经完成了应用程序的开发和测试,现在准备提交生产发布.问题是,某些 Web 服务 URLs 指向测试服务器,而 API keys 则为测试环境而配置.在提交应用程序给苹果审 ...

  10. SQL分组查询GroupBy

    一.分组查询1.使用group by进行分组查询在使用group by关键字时,在select列表中可以指定的项目是有限制的,select语句中仅许以下几项:〉被分组的列〉为每个分组返回一个值得表达式 ...