这篇文章做了一个很好的测试: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. 【python】坑,坑,折腾一个下午python 3.5中 ImportError: No module named BeautifulSoup

    将语句 from bs4 import BeautifulSoup4 改成 from bs4 import BeautifulSoup 通过 尼玛------------------------! 总 ...

  2. CSS学习_属性选择器

    CSS选择器参考 [attribute]——选取带有指定属性的元素: [attribute=value]——选取带有指定属性和值的元素: [attribute~=value]——选取属性值中包含指定词 ...

  3. hdu 4099 Revenge of Fibonacci Trie树与模拟数位加法

    Revenge of Fibonacci 题意:给定fibonacci数列的前100000项的前n位(n<=40);问你这是fibonacci数列第几项的前缀?如若不在前100000项范围内,输 ...

  4. hdu 1358 period KMP入门

    Period 题意:一个长为N (2 <= N <= 1 000 000) 的字符串,问前缀串长度为k(k > 1)是否是一个周期串,即k = A...A;若是则按k从小到大的顺序输 ...

  5. C++引用计数

    简介 引用计数就是对一个对象记录其被引用的次数,其的引用计数可加可减,那什么时候加什么时候减呢?所以引用计数的重点在于在哪里加,在哪里减: 加: 减: 实现 // // Ref.hpp // Ref ...

  6. maven 添加Sqlserver的jdbc jar包

    maven添加sqlserver的jdbc驱动包 jdbc.jar download url->http://pan.baidu.com/s/1hrEhdti 通过maven命令将jar包安装到 ...

  7. *[topcoder]LittleElephantAndString

    http://community.topcoder.com/stat?c=problem_statement&pm=12854&rd=15709 这道题DIV1 250的,还有点意思. ...

  8. 用C++ 设计一个不能被继承的类

    http://blog.sina.com.cn/s/blog_69d9bff30100odlz.html 在Java 中定义了关键字final ,被final 修饰的类不能被继承.但在C++ 中没有f ...

  9. tshark 使用说明

    yum install -y wireshark 最近才发现,原来wireshark也提供有Linux命令行工具-tshark.tshark不仅有抓包的功能,还带了解析各种协议的能力.下面我们以两个实 ...

  10. WCF - Hosting WCF Service

    After creating a WCF service, the next step is to host it so that the client applications can consum ...