Java Object的hashCode方法及其重写应用

news2025/7/17 0:42:55

本文参考:

Object中的hashCode()终于搞懂了!!!_马走日mazouri的博客-CSDN博客_object hashcode

Java加密:常见哈希算法总结_m.j.y.的博客-CSDN博客

Java Object类中的hashCode方法_qq_18974899的博客-CSDN博客_object类中的hashcode方法

Java对象的hashcode()方法_刘艾可的博客-CSDN博客_对象的hashcode

Java中的Hash值的计算方式,java哈希算法简单数据类型的具体实现_了迹奇有没的博客-CSDN博客

摘要:

深入浅出说清楚Java Object的hashCode方法及其在Integer、String以及Objects类中的重写,以及在HashMap中的应用

文章目录

    • Object中的hashCode方法
    • 哈希碰撞(哈希冲突)
    • Integer类型重写的hashCode()
    • String类型重写的hashCode()方法
    • Objects的hash(Object... values) 方法
    • HashMap中的hashCode()和equals()

Object中的hashCode方法

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

@IntrinsicCandidate
public native int hashCode();

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

在openjdk8根路径/hotspot/src/share/vm/runtime路径下的synchronizer.cpp文件中,有生成哈希值的代码,默认为Marsaglia XORshift随机数算法。

对于默认的Marsaglia XORshift随机数算法需要了解的是,不同对象的结果一般不同,即使对象的属性相同,生成的结果也不一定相同,事实上大部分文章都认为它是和生成的对象的堆内存地址相关的,即使是相同属性的对象,它们在堆中的内存也通常不同,确实可以这么理解,但是从源码中看出来是不太准确的

/**
     * 测试HashCode生成
     * -XX:hashCode=
     * 0 - Park-Miller RNG: seed = (16807*seed) % (2147483947)
     * 1 - 跟内存地址有关
     * 2 - 固定返回1
     * 3 - 自增
     * 4 - 跟内存地址有关
     * 默认 - Marsaglia XORshift随机数算法
     */
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; //返回hash值
}

源码中的hashCode其实是JVM启动的一个参数,每一个分支对应一个生成策略,通过-XX:hashCode可以切换hashCode的生成策略。
下面验证第2种生成策略,用软件idea输入参数-XX:hashCode=2,可以看到输出结果正是1,从而进一步验证了上面的源码。
在这里插入图片描述

测试和运行结果如下:
在这里插入图片描述

哈希碰撞(哈希冲突)

哈希碰撞是指,在计算哈希值时,两个不同对象的输入得到了相同的输出,比如:

@Test
public void test2(){
    System.out.println("AaAaAa".hashCode()); // 1952508096
    System.out.println("BBAaBB".hashCode());; // 1952508096

    System.out.println("通话".hashCode());; // 1179395
    System.out.println("重地".hashCode()); // 1179395
}

碰撞能不能避免?答案是不能。碰撞是一定会出现的,因为输出的字节长度是固定的,hashcode()输出是4字节整数,最多只有4294967296种输出,但输入的数据长度是不固定的,有无数种输入。所以,哈希算法是把一个无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。

冲突解决策略:

  • 1、分离链表法(拉链法)

    在每一个相同的散列地址上构建链表。

  • 2、闭散列方法(开放地址法)

Integer类型重写的hashCode()

直接返回原值

    /**
     * Returns a hash code for this {@code Integer}.
     *
     * @return  a hash code value for this object, equal to the
     *          primitive {@code int} value represented by this
     *          {@code Integer} object.
     */
    @Override
    public int hashCode() {
        return Integer.hashCode(value);
    }

    /**
     * Returns a hash code for an {@code int} value; compatible with
     * {@code Integer.hashCode()}.
     *
     * @param value the value to hash
     * @since 1.8
     *
     * @return a hash code value for an {@code int} value.
     */
    public static int hashCode(int value) {
        return value; // 直接返回原值
    }

包装类型全都重写了hashcode方法
1)、Byte、Short、Integer、Long对应的hashcode方法,均是返回原值

2)、Character对应的hashcode方法,返回的是字符对应的ASCII码值(包含0-9、a-z、A-Z以及一些字符)

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

3)、Double、Float对应的hashcode方法,返回的是经过计算的值,与地址无关,也和数据类型无关

  @Override
    public int hashCode() {
        return Double.hashCode(value);
    }
    
     public static int hashCode(double value) {
        long bits = doubleToLongBits(value);
        return (int)(bits ^ (bits >>> 32));
    }
    
       public static long doubleToLongBits(double value) {
        long result = doubleToRawLongBits(value);
        // Check for NaN based on values of bit fields, maximum
        // exponent and nonzero significand.
        if ( ((result & DoubleConsts.EXP_BIT_MASK) ==
              DoubleConsts.EXP_BIT_MASK) &&
             (result & DoubleConsts.SIGNIF_BIT_MASK) != 0L)
            result = 0x7ff8000000000000L;
        return result;
    }

4)、Boolean对应的hashcode方法,返回的是固定的两个值,true返回1231,false返回1237

@Override
public int hashCode() {
  return Boolean.hashCode(value);
}

public static int hashCode(boolean value) {
  return value ? 1231 : 1237;
}

String类型重写的hashCode()方法

java.lang.String类重写了hashCode和equals方法,重写后的hashCode、和equals方法和内部的值有关:

重写hashCode()方法:

public int hashCode() {
        // The hash or hashIsZero fields are subject to a benign data race,
        // making it crucial to ensure that any observable result of the
        // calculation in this method stays correct under any possible read of
        // these fields. Necessary restrictions to allow this to be correct
        // without explicit memory fences or similar concurrency primitives is
        // that we can ever only write to one of these two fields for a given
        // String instance, and that the computation is idempotent and derived
        // from immutable state
        int h = hash;
        if (h == 0 && !hashIsZero) {
            h = isLatin1() ? StringLatin1.hashCode(value)
                           : StringUTF16.hashCode(value);
            if (h == 0) {
                hashIsZero = true;
            } else {
                hash = h;
            }
        }
        return h;
    }

public static int hashCode(byte[] value) {
        int h = 0;
        for (byte v : value) {
            h = 31 * h + (v & 0xff);
        }
        return h;
    }

重写equals(Object anObject):

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

测试代码:

@Test
public void test3(){
  String str1="abc";
  String str2="abc ";
  System.out.println(str1.hashCode());
  System.out.println(str2.hashCode());
}

输出:

96354
2987006

"abc"的hashcode值为96354,那么即为 31*(31*(31*0+97)+98)+99=96354

"abc ",多了个空格,空格的ASCII码值为32,所以 "abc " hashcode 为 96354*31+32 = 2987006

Objects的hash(Object… values) 方法

这个方法常用来重写原来Object的hashCode()方法,可变参数作为输入,可以传入对象的属性,从而根据属性生成哈希值,调用Arrays.hashCode(values)。

// 可变参数作为输入
public static int hash(Object... values) {
        return Arrays.hashCode(values);
}

// 调用Arrays.hashCode(values)
public static int hashCode(Object[] a) {
        if (a == null)
            return 0;

        int result = 1;

        for (Object element : a) // 根据传入的属性生成哈希值
            result = 31 * result + (element == null ? 0 : element.hashCode());

        return result;
}

HashMap中的hashCode()和equals()

hashCode() 和 equals()本质上没有任何关系,关键是看你要用到的时候,是不是会创建“类对应的散列表”,像HashSet, Hashtable, HashMap这些容器都会创建「数组」这种散列表。有些equals()纯粹就是比较下对象属性的,不存在散列表,就不需要重写hashCode()函数。如果HashSet没有重写HashCode(),那它存放元素的时候先会用hash(hashCode())%n计算存放元素的桶的位置,再调用equals()比较,如果不重写,大概率属性相等的元素引用地址不同,就不会存在一个桶里,也就不能比较了。

小栗子测试:

package com.jxz.hashCodeTest;

import java.util.HashMap;

public class Person {
    int id;
    String name;

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

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

输出:

null

我们期望通过map.get(new Person(1,"jxz"))获取该对象的value,但上面代码的输出结果为null。本质是map的key不一样,不能取到对应的值。原因一就在于Person类中没有覆盖hashCode方法,从而导致两个属性相同的实例具有不同的哈希值,原因二在于没有重写equals(),现在默认比较的还是两个堆对象的地址,不是比较对象内容。HashMap中get()的核心代码如下:

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

// 上面hash变量的计算
static final int hash(Object key) {
  int h;
  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

判重的时候同时用上了hashCode()和equals(),if条件表达式中的e.hash == hash是先决条件,只有相等才会执行&&后面的代码,这样&&短路操作一旦生效,会极大提高程序的效率。hashCode()不同的话对象一定不同;hashCode()相同的话,equals()比较内容相同的话,对象才相同。

因此重写上面的equals和hashCode()方法,让它们根据对象的属性进行比较,用到了Objects的equals和hash方法:

package com.jxz.hashCodeTest;

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

public class Person {
    int id;
    String name;

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

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

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

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

输出:

100

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/369048.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java行转列通用工具类适用于各种查询情况

1、说明 有时候工作中需要动态生成列&#xff0c;也就是不确定的列&#xff0c;那么在数据库层就不是那么好操作了&#xff0c;可以使用java工具类来实现。 本工具类是对市面上的工具类进行加工改造&#xff0c;可以通用于各种情况&#xff0c;更加灵活&#xff0c;下面我来演…

GitHub

什么是 Github?GitHub是一个面向开源及私有软件项目的托管平台&#xff0c;因为只支持Git作为唯一的版本库格式进行托管&#xff0c;故名GitHub。一、常用词Watch&#xff1a;观察。如果watch了一个项目&#xff0c;之后这个项目有更新&#xff0c;你会在第一时间收到该项目更…

pytorch1.2.0+python3.6

一、说明 pytorch1.2.0python3.6CUDA10.0cudnn7.4.1.5 二、步骤 在conda中创建一个新的虚拟环境 查看一下自己的所有环境 激活虚拟环境 conda activate torch1.2.0 关于cuda和cudnn 1、查看自己电脑系统是10.2版本 http://链接&#xff1a;https://pan.baidu.com/s/1v5cN6…

Vivado_FIR滤波器输出位宽计算方法

计算方法 全精度输出宽度可以定义为输入数据宽度加上由滤波器系数导致的位增长数。 最坏情况下的位增长等于系数宽度加上所需非零乘法次数的以2为底的对数并四舍五入后的值。然而&#xff0c;这没有考虑实际系数值。计算公式如下。 BCWceil⁡[log⁡2N]BC_{W}\operatorname{cei…

滑台模组的应用有哪些?

在自动化生产中&#xff0c;我们常常会看到滑台模组的身影&#xff0c;那么&#xff0c;滑台模组究竟在自动化生产设备中起着怎样的作用呢&#xff1f; 简单点说&#xff0c;滑台模组由滑块、滚珠丝杆、导轨、主体等其它传动零件组成的自动化晋级单元&#xff0c;经过各单元的组…

储物流行业解决方案

行业分析 第三方物流仓储物流的日常管理控制活动主要包括进,出&#xff0c;存三个方面。在没有实现计算机化管理的商业企业中&#xff0c;大量的业务操作和管理活动由人工来完成。在管理层中&#xff0c;由于大量必要的信息不能及时被采集﹑加工和整理使用&#xff0c;造成了极…

想成为一名专业黑客,但不知道从哪里学起?我来教你。

成为一名黑客需要学什么&#xff1f; 想成为一名专业黑客&#xff0c;但不知道从哪里学起”很多人在后台问过这个问题&#xff0c;今天就为你介绍成为专业黑客必须学习的十个方面的知识&#xff0c;希望能为迷惘中的你指明方向。 想要成为网络hacker黑客&#xff1f;先来学习…

未来土地利用模拟FLUS模型

未来土地利用模拟&#xff08;FutureLand-Use Simulation, FLUS&#xff09;模型1 模型简介1.1 基于ANN 的适宜性概率计算1.2 基于自适应惯性机制的元胞自动机1.3 模拟精度评价参考流域 径流变化是 自然因素和 人为因素共同作用的结果&#xff0c;其中人为因素最为直接的方式就…

SpringMVC框架知识详解(入门版)

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

【mysql 5.7】基础入门(一)

文章目录1.常用命令2.SQL语句3导入数据4.DQL4.1 查询一个字段4.2 查询多个字段4.3 查询所有字段4.4 列起别名4.5 列参与数学运算4.6 条件查询4.7 排序4.8 多个字段排序5.函数5.1 单行处理函数5.2 分组函数&#xff16;&#xff0e;分组查询1.常用命令 退出mysql exit;查看有哪…

Redis 集群搭建

前缀参考文章1&#xff1a;Centos7 安装并启动 Redis-6.2.6 前缀参考文章2&#xff1a;Redis 主从复制-服务器搭建【薪火相传/哨兵模式】 管道符查看所有redis进程&#xff1a;ps -ef|grep redis 杀死所有redis进程&#xff1a;killall redis-server 1. 首先修改 redis.conf 配…

Jmeter(GUI模式)详细教程

Jmeter&#xff08;GUI模式&#xff09;详细教程 目录&#xff1a;导读 一、安装Jmeter 二、Jmeter工作原理 三、Jmeter操作步骤 Jmeter界面 1、测试计划 2、线程组 3、HTTP请求 4、监听器 四、压力测试 写在最后 前些天&#xff0c;领导让我做接口的压力测试。What…

cocos2dx+lua学习笔记:UIScrollView的使用

前言 本篇在讲什么 本篇简单介绍Lua篇cocos2dx中UIScrollView的相关内容 仅介绍简单的应用&#xff0c;仅供参考 本篇适合什么 适合初学Cocos2dX的小白 适合想要在Cocos2dx-lua中使用UIScrollView的人 本篇需要什么 对Lua语法有简单认知 对Cocos2dx-Lua有简单认知 Co…

【JavaSE】复习(基础)

文章目录基础1.1. public class 和 class1.2. 字面量1.3. 变量的引出1.4. javadoc的使用1.5. 转义字符1.6. 逻辑运算1.7. 用户键盘输入1.8. switch1.9. for循环1.10.方法的调用1.11.break return1.12.方法重载&#xff08;overload&#xff09;1.13.成员变量中的实例变量1.14.方…

计算机网络你都懂了吗

文章目录一、计算机网络的定义简单定义通用定义二、计算机网络通信过程三、什么是网络协议&#xff08;Protocol&#xff09;四、网络协议组成及功能一、计算机网络的定义 简单定义 计算机网络是一些相互连接的、自治的计算机系统的集合。 通用定义 将处于不同位置并具有独…

ChatGPT?听说Biying把它下架了

ChatGPT被玩疯了&#xff0c;开始放飞自我 ChatGPT版微软必应上线不到10天…就被网友玩坏了 先说这个词&#xff0c;放飞自我&#xff0c;什么东西才会放飞自我&#xff1f; 人放飞自我&#xff0c;人&#xff1f;你确定是人&#xff1f; 所以让我们来把上面的句子改写一下。…

怎样激发读者好奇心?短视频营销之场景化

目录 激发读者好奇心&#xff1f;四个小技巧帮你搞定 1.省略法 2.欲言又止法: 3.问句法:就是用疑问的形式引起别人的好奇。 4.反差法 选择合适的主题。 利用场景化效果 使用滤镜。 如何提高用户的留存率。 1、设置一个有趣的话题。 2、用好道具。 3、多用竖屏。 什…

适合python游戏开发的库你知道几个?

python游戏开发的库 01 PyGame 官网&#xff1a; https://www.pygame.org/docs/ 概述&#xff1a; Pygame 是一组专为编写视频游戏而设计的 Python 模块。 它在优秀的 SDL 库之上添加了功能。这允许您使用 python 语言创建功能齐全的游戏和多媒体程序。Pygame具有高度的可…

即将报名,如何选择软考考试科目?

软考全称为计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试 是职业资格、专业技术资格考试&#xff0c;也是职称考试。 计算机软件资格考试设置了27个专业资格&#xff0c;涵盖5个专业领域&#xff0c; 3个级别层次&#xff08;初级、中级、高级&#xff09; …

ubuntu20.04安装conda

1)conda与miniconda 任何语言的包、依赖和环境管理---Python, R, Ruby, Lua, Scala, Java, JavaScript, C/ C, FORTRAN。Conda 是一个运行在 Windows、macOS 和 Linux 上的开源包管理系统和环境管理系统。Conda 可以快速安装、运行和更新包及其依赖项。Conda 可以轻松地在本地计…