关于java中的hashcode和equals方法原理

1、介绍

java编程思想和很多资料都会对自定义javabean要求必须重写hashcode和equals方法,但并没有清晰给出为何重写此两个方法,至少不是非常的明确。

首先要确定的一件事是并不是“必须”,估计跟中英文语言习惯有关。hashcode方法只有在和hash类型的集合(比如HashMap和HashSet)配合使用时才会进行调用,否则是没有必要重写该方法的。

所以很多人会迷惑,自己并没有重写这方法,程序跑起来也没有问题。要说明这个问题必须要搞懂hashcode的真正作用以及使用场景。

2、hashcode

hashcode是java中Objet类定义的方法,默认返回的是对象的内存地址。但该方法的本意是散列。散列的话就必然涉及到在一定的空间中进行散列,所以hashcode方法一定是和集合配合使用的时候才用得到。

对象在空间散列化存储之后,其优势在于检索,如果散列算法处理得好,也就是能够保证对象在空间中尽可能均匀分布,则在检索时,一旦确定桶的方位(即下标值),就可以排除(n-1)/n的数据量,所有在大型数据集合中,hash之后的对象检索性能是非常高的。

java中的HashMap集合的内部实现是数组+链表实现,即Node[]数组方式实现的。而Node的源代码如下:

static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}

说明Node节点类是一种手拉手实现的链表方式,而Node[]在java编程思想中称为桶集合,数组中每个元素可以看成一个桶。HashMap集合在初始化时会先分配16个桶的空间,在进行put操作时,会先将KV封装成Node对象,再对key进行计算,判断应该划分到哪个桶中。

注意key在集合中是“不重复”的,因此如果key存在,就将新的value替换掉旧的value,如果key不存在,就在链表的末尾进行添加。如何判断key是否重复?HashMap类中的putVal方法源码如下:请注意注释地方的判断条件是

p.hash == hash
&&((k = p.key) == key || (key != null && key.equals(k))),

首先明确p是KV构成Node对象,该对象源码中可见含有四个属性,分别是int hash、K key、V value和Node<K,V> next,其中hash并不是key中的hashcode,是对key的hashcode计算之后生成的新hash值,我们称为新哈希,而生成新hash的算法是:

	int newhash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

翻译过来就是将旧哈希(我们称key对象的hashcode为就hash)向右移动16位之后和自身做异或运本意就是将高16位和低16位的值进行异或运算得到一个新的数值,这么做的意图非常明显,高位移位运算是想让更多的特征值参与进来,采用异或计算是想让数据更加分散。我们知道二进制位运算中有|、&、和~,很明显~是单元运算,如果采用|运算记录计算结果很大比例偏大,而采用&很大比例计算结果偏小,运算刚好高低比例相同。如此看来,设计也是非常巧妙的。



上面的判断条件翻译过来,就是:如果两个节点的新hash不同,则key一定不同。但如果新hash相同,还要判断key是否是同一对象,若是同一对象,则说明key相同,若不是同一对象,再判断equals方法是否相同。可以使用如下判断语句来描述获取更加清晰易懂:

if(newhash1 != newhash2){
//不同
}
else{
if(key1 == key2){
//相同
}
else{
if(key1.equals(key2)){
//相同
}
else{
//不同
}
}
}

桶的防止策略是对新hash对(桶数量-1)进行&运算产生的结果作为桶的下标值,由此可以看出,桶的数量一定是2的n次幂,默认是16只桶,即2^4次方,扩容以后会32,64以此类推下去。算法如下:

(n - 1) & hash		//(16 - 1) & hash与取摸操作是等效的。

3、equals方法

equals方法比较简单,就是判断对象内容是否相同,默认实现是判断内存地址。在java的集合中,List并不判断对象hashcode值,只判断equals方法。

4、总结

java集合中,HashMap和HashSet使用hashcode进行对象的散列存储,因此会用到hashcode方法,自然也会用到equals方法,List集合只使用equals方法判断对象是否相等。



在java的集合中,真正的集合只有数组和链表两种实现,HashMap是通过两者组合实现的,而HashSet内部是通过HashMap实现的,丢弃了HashMap中的value部分,使用了一个垃圾值(dummy)进行填充实现的。



所以究其根本,ArrayList和LinkedList应该是最基本的集合,数组列表内部封装数组,擅长读操作,大量写入时(尤其是在队首插入数据)性能较差,因为需要移动所有元素,而LinkdedList在写入时非常有优势,查询则较差,两者各有优缺点,在使用单机进行百万数据读写的评测中,数组列表读取能是列表是进10倍,而列表的写入能力是数组列表的10倍以上,差距还是非常明显的。

关于java中的hashcode和equals方法原理的更多相关文章

  1. K:java中的hashCode和equals方法

      hashCode和equals方法是Object类的相关方法,而所有的类都是直接或间接的继承于Object类而存在的,为此,所有的类中都存在着hashCode和equals.通过翻看Object类 ...

  2. java中的hashcode()和equals()

    equals()和hashcode()都继承自object类. equals() equals()方法在object类中定义如下: public boolean equals(Object obj) ...

  3. Java中的hashCode() 和 equals()的若干问题解答

    一.hashCode()的作用 哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int ...

  4. Java中的“==操作符”和equals方法有什么区别

    Java中的"=="和equals方法究竟有什么区别? 1.==操作符 "=="操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的 ...

  5. Java 重写 hashCode() 和 equals() 方法

    1. hashCode 1.1 基本概念 hashCode 是 JDK 根据对象的地址算出来的一个 int 数字(对象的哈希码值),代表了该对象再内存中的存储位置. hashCode() 方法是超级类 ...

  6. 有关java中的hashCode问题

    1. HashSet集合存储数据的结构(哈希表) 1.1 什么是哈希表? 哈希表底层使用的也是数组机制,数组中也存放对象,而这些对象往数组中存放时的位置比较特殊,当需要把这些对象给数组中存放时,那么会 ...

  7. JAVA中的各种 哈希码(HashCode) 与 equals方法在HIBERNATE的实际应用[转载]

    1.什么是哈希码(HashCode) 在Java中,哈希码代表对象的特征.例如对象 Java代码 String str1 = “aa”, str1.hashCode= 3104 String str2 ...

  8. Java 中正确使用 hashCode 和 equals 方法

    在这篇文章中,我将告诉大家我对hashCode和equals方法的理解.我将讨论他们的默认实现,以及如何正确的重写他们.我也将使用Apache Commons提供的工具包做一个实现. 目录: hash ...

  9. (转)Java 中正确使用 hashCode 和 equals 方法

    背景:最近在编写持久化对象时候遇到重写equals和hashCode方法的情况,对这两个方法的重写做一个总结. 链接:https://www.oschina.net/question/82993_75 ...

随机推荐

  1. [Android UI]View基础知识

    一.简介 在安卓中,View代表视图,是安卓中十分重要的一个概念,重要程度不亚于四大组件,用户每时每刻都在与View打交道,包括展示数据.事件传递等.因此,熟练掌握View的应用以及原理是Androi ...

  2. C语言中Extern用法

    extern用在变量或函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”. extern修饰变量的声明. 举例:若a.c中需引用b.c中的变量int v,可以在a.c中声明extern ...

  3. hdu2067 小兔的棋盘

    小兔的棋盘 时间限制:1000/1000 MS(Java / Others)内存限制:32768/32768 K(Java / Others)总提交内容:13029接受的提交内容:6517 问题描述 ...

  4. day 012 生成器 与 列表推导式

    生成器的本质就是迭代器,写法和迭代器不一样,用法一样. 获取方法: 1.通过生成器函数 2.通过各种推导式来实现生成器 3.通过数据的转换也可以获取生成器 例如: 更改return 为 yield 即 ...

  5. 解决报错:import sun.misc.BASE64Decoder无法找到

    解决报错:import sun.misc.BASE64Decoder无法找到 2017年09月29日 16:03:26 chaoyu168 阅读数:2116 标签: sun.misc.BASE64De ...

  6. mybatis主键是在insert前生成还是之后生成

    oracle sequence 推荐每个表使用自己的sequence mysql 使用每个表的autoincreate来当主键 mybatis 操作insert时 主键的生成是在插入之前 还是之后? ...

  7. c语言中有关0和1的运算问题

    /*有关0和1 的总结 最近做题总是混淆0 和 1 对于/ 和 %运算时候的结果怎么算 所以就上机试验了一番 结论: c语言中,0/任何数都为0 0%任何数都为0 1/任何数都为0 1%任何数都余1 ...

  8. 进入wordpress中的模板文件

    Wordpress的页面结构 一个简单的wordpress由 头部.内容.页脚组成,每个块中的每一个都由当前wordpress主题中模板文件生成. eg: 头:包含重要的信息,一般都是网页的头部. 内 ...

  9. mgo01_window server 2012上安装mongo4.0

    目前mongo最新版为4.0(2018-07-18),下载地址 https://fastdl.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-4 ...

  10. java——volatile的可见性不能保证线程安全

    volatile: 1.保证变量对所有线程的可见性(但是由于java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的) 用代码试过,确实是这样的,原因:有可能同时多个thr ...