我的编程空间,编程开发者的网络收藏夹
学习永远不晚

搞懂JAVAObject中的hashCode()

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

搞懂JAVAObject中的hashCode()

Object中的hashCode()

hashCode方法用来返回对象的哈希值,提供该方法是为了支持哈希表,例如HashMap,HashTable等,在Object类中的代码如下:


 public native int hashCode();

这是一个native声明的本地方法,返回一个int型的整数。由于在Object中,因此每个对象都有一个默认的哈希值。

在openjdk8根路径/hotspot/class="lazy" data-src/share/vm/runtime路径下的synchronizer.cpp文件中,有生成哈希值的代码:


static inline intptr_t get_next_hash(Thread * Self, oop obj) {
  intptr_t value = 0 ;
  if (hashCode == 0) {
     // 返回随机数
     value = os::random() ;
  } else
  if (hashCode == 1) {
     //用对象的内存地址根据某种算法进行计算
     intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
     value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
  } else
  if (hashCode == 2) {
     // 始终返回1,用于测试
     value = 1 ;            
  } else
  if (hashCode == 3) {
     //从0开始计算哈希值
     value = ++GVars.hcSequence ;
  } else
  if (hashCode == 4) {
     //输出对象的内存地址
     value = cast_from_oop<intptr_t>(obj) ;
  } else {
     // 默认的hashCode生成算法,利用xor-shift算法产生伪随机数
     unsigned t = Self->_hashStateX ;
     t ^= (t << 11) ;
     Self->_hashStateX = Self->_hashStateY ;
     Self->_hashStateY = Self->_hashStateZ ;
     Self->_hashStateZ = Self->_hashStateW ;
     unsigned v = Self->_hashStateW ;
     v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
     Self->_hashStateW = v ;
     value = v ;
  }

  value &= markOopDesc::hash_mask;
  if (value == 0) value = 0xBAD ;
  assert (value != markOopDesc::no_hash, "invariant") ;
  TEVENT (hashCode: GENERATE) ;
  return value;
}

源码中的hashCode其实是JVM启动的一个参数,每一个分支对应一个生成策略,通过-XX:hashCode可以切换hashCode的生成策略。

下面验证第2种生成策略,用软件idea输入参数-XX:hashCode=2,可以看到输出结果正是1,从而进一步验证了上面的源码。

在这里插入图片描述

在这里插入图片描述

hashCode()和equals()

hashCode()和equals()用来标识对象,两个方法协同工作用来判断两个对象是否相等。对象通过调用 Object.hashCode()生成哈希值,由于不可避免地会存在哈希值冲突的情况 因此hashCode 相同时 还需要再调用 equals 进行一次值的比较,但是若hashCode不同,将直接判定两个对象不同,跳过 equals ,这加快了冲突处理效率。 Object 类定义中对 hashCode和 equals 要求如下:

  • 如果两个对象的equals的结果是相等的,则两个对象的 hashCode 的返回结果也必须是相同的。
  • 任何时候重写equals,都必须同时重写hashCode。

下面看一个小例子:


import java.util.HashMap;
import java.util.Objects;


public class Person {
    //用户Id,唯一确定用户
    private String id;
    private String name;

    public Person(String id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person person = (Person) o;
        return Objects.equals(id, person.id) && Objects.equals(name, person.name);
    }

    public static void main(String[] args) {
        HashMap<Person, Integer> map = new HashMap<>();
        //key:Person类型  value:Integer类型
        map.put(new Person("1","张三"),100);
        System.out.println(map.get(new Person("1", "张三")));
    }
}

我们将Person类的实例作为key,value为这个对象的考试成绩。我们期望通过map.get(new Person("1", "张三"))获取该对象的考试成绩,但上面代码的输出结果为null。原因就在于Person类中没有覆盖hashCode方法,从而导致两个相等的实例具有不同的哈希值。HashMap中get()的核心代码如下


if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
     return first;

if条件表达式中的e.hash == hash是先决条件,只有相等才会执行&&后面的代码。equals不相等时并不强制要求哈希值相等,但是一个优秀的哈希算法应尽可能让元素均匀分布,降低冲突发生的概率,即在equals不相等时尽量让哈希值也不相等,这样&&或||短路操作一旦生效,会极大提高程序的效率。像上面的例子,因为没有重写hashCode方法,两个对象有两个哈希值,获取对象时可能在别的哈希桶中查找,即使凑巧在一个哈希桶,因为哈希值不一样,也找不到原来那一个对象。
你可以根据你自己的需求设计重写hashCode方法,或者调用JDK提供好的,比如


  @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }

这样就能解决问题,但是这个运行速度慢一些,因为它们会引发数组的创建,以便传入数目可变的参数, 如果参数中有基本类型,还需要装箱和拆箱 ,建议只将这类散列函数用于不太注重性能的情况。

重写的hashCode()方法

Java为许多常用的数据类型重写了hashCode()方法,比如String,Integer,Double等。比如在Integer类中哈希值就是其int类型的数据。


  public static int hashCode(int value) {
        return value;
    }

总结

本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注编程网的更多内容!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

搞懂JAVAObject中的hashCode()

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

一文搞懂python中Tkinter的使用

本篇文章给大家带来了关于Python的相关知识,其中主要整理了Tkinter的相关问题,Tkinter 是使用 python 进行窗口视窗设计的模块,下面一起来看一下,希望对大家有帮助。【相关推荐:Python3视频教程 】一、前言1.1、Tkinter是什么Tkinter 是使用 python 进行窗口视窗设计的模块。Tkinter模块(“Tk 接口”)是Python的标准Tk GUI工具包的接口
2022-07-04

一文搞懂Python中is和==的区别

is和==都是对对象进行比较判断作用的,但对对象比较判断的内容并不相同,下面来看看具体区别在哪?对Python中is和==的区别感兴趣的朋友跟随小编一起看看吧
2023-01-10

如何搞懂Java8中的时间处理

今天就跟大家聊聊有关如何搞懂Java8中的时间处理,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。前言java8借鉴了第三方日期库joda很多的优点java.time包类名描述Inst
2023-06-26

一文带你搞懂Java中的递归

这篇文章主要为大家详细介绍了Java中的递归的实现以及应用,文中的示例代码讲解详细,对我们学习Java有一定帮助,需要的可以参考一下
2022-11-13

一文带你搞懂Vue中Vuex的使用

​ Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态。本文会通过一些简单的示例,为大家详细讲解Vuex的使用,感兴趣的小伙伴可以跟随小编一起学习一下
2022-11-13

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录