首页 > 编程语言 >[Java] String的hashCode方法

[Java] String的hashCode方法

时间:2024-11-19 17:57:21浏览次数:1  
标签:code hash String java 31 value hashCode Java

简述

  • java/lang/String#hashCode是用途极广的方法,其源码实现也存在一定变迁。

其位于 JRE 的 rt.jar 包内

OpenJDK

OpenJDK 8-b120版 ~ 9-b00版 := Oracle JDK 1.8.0-261

  • jdk/jdk/src/share/classes/java/lang/String.java

https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/java/lang/String.java
https://github.com/openjdk/jdk/blob/jdk9-b00/jdk/src/share/classes/java/lang/String.java

    /**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

OpenJDK 9-b38版 ~ 9-b65版

  • jdk/src/java.base/share/classes/java/lang/String.java

https://github.com/openjdk/jdk/blob/jdk9-b38/jdk/src/java.base/share/classes/java/lang/String.java

    /**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    public int hashCode() {
        int h = hash;
        if (h == 0) {
            for (char v : value) {
                h = 31 * h + v;
            }
            hash = h;
        }
        return h;
    }

OpenJDK 9-b66版 ~ 9-b92版

https://github.com/openjdk/jdk/blob/jdk9-b66/jdk/src/java.base/share/classes/java/lang/String.java
https://github.com/openjdk/jdk/blob/jdk9-b92/jdk/src/java.base/share/classes/java/lang/String.java

    /**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    public int hashCode() {
        int h = hash;
        if (h == 0) {
            for (char v : value) {
                h = 31 * h + v;
            }
            if (h != 0) {
                hash = h;
            }
        }
        return h;
    }

OpenJDK 9-b93版 ~ 10+1版 ~ unknowVersion

  • jdk/src/java.base/share/classes/java/lang/String.java

https://github.com/openjdk/jdk/blob/jdk9-b93/jdk/src/java.base/share/classes/java/lang/String.java
https://github.com/openjdk/jdk/blob/jdk-10%2B1/jdk/src/java.base/share/classes/java/lang/String.java

    /**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    public int hashCode() {
        if (hash == 0 && value.length > 0) {
            hash = isLatin1() ? StringLatin1.hashCode(value) : StringUTF16.hashCode(value);
        }
        return hash;
    }

OpenJDK unknowVersion ~ 10+1版 ~ 24+24版

  • jdk/src/java.base/share/classes/java/lang/String.java

https://github.com/openjdk/jdk/blob/jdk-11%2B28/src/java.base/share/classes/java/lang/String.java
https://github.com/openjdk/jdk/blob/jdk-24%2B24/src/java.base/share/classes/java/lang/String.java

    /**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    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;
    }

Huawei JDK

Huawei JDK 1.8/25.412-b08

hashCode

OpenJDK 64-Bit Server VM - Huawei Technologies Co., Ltd - 1.8/25.412-b08 JAVA_HOME: /usr/local/huaweijre-8 /usr/local/huaweijre-8/lib/rt.jar注: 华为云 DLI Flink 1.12 即使用的此版 JDK,如下代码为反编译后的结果: (与 Oracle JDK 1.8.0-261相比,其区别在于:int=>byte`)

  public int hashCode() {
    int i = this.hash;
    if (i == 0 && this.value.length > 0) {
      char[] arrayOfChar = this.value;
      for (byte b = 0; b < this.value.length; b++)
        i = 31 * i + arrayOfChar[b]; 
      this.hash = i;
    } 
    return i;
  }

Oracle JDK 1.8.0-261 vs. Huawei JDK 1.8/25.412-b08

试验代码

public static int hashCode(char [] value){  
    //char [] value = stringThis.value;  
    int var1 = 0; //this.hash;//this.hash 的起始值=0 | 本方法的计算结果(hash) 即 var1    if (var1 == 0 && value.length > 0) {  
        char[] var2 = value;  
  
        for(int var3 = 0; var3 < value.length; ++var3) {//int vs byte
            var1 = 31 * var1 + var2[var3];  
        }  
        //this.hash = var1;  
    }  
    return var1;  
}  
  
public static void main(String[] args) {  
    char [] value = "Hello".toCharArray(); //new char[] { 'H', 'e', 'l', 'l', 'o' };
    Long totalTimeConsuming = 0L;  
    int count = 1000;  
    for(int i=0;i<=count;i++){  
        Long startTime = System.nanoTime();  
        int hashcode = hashCode(value);  
        Long timeConsuming = System.nanoTime() - startTime;//nano time 纳秒 | 1纳秒 = 0.001 微秒 ; 1纳秒=0.000001 毫秒  
        totalTimeConsuming += timeConsuming;  
        log.info("hashCode: {}, timeConsuming : {}ns", hashcode, timeConsuming);  
    }  
    double avgTime = (double) totalTimeConsuming/count;//2个整型相除,需用浮点型接收,以避免精度丢失  
    log.info("avg time consuming : {} ns", avgTime );//  
}

功能对比结果:

hashCode 运算结果未发生改变

性能对比结果:

循环1000次(试验5轮),求平均值:

hashcode : byte 版 (Huawei JDK 1.8/25.412-b08)	
	552.1 ns
	626.3 ns
	477.2 ns
	643.8 ns
	648.2 ns
	=====> 总体 avg : 589.52 ns

hashcode : int 版 (Oracle JDK 1.8.0-261)
	633.8 ns
	577.4 ns
	553.4 ns
	517.7 ns
	788.0 ns
	====> 总体 avg : 614.06 ns

FAQ

Q:为什么String中hashCode方法里使用神奇因子 31呢?

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}
  • hashCode 方法核心的计算逻辑只有3行,也就是代码中的 for 循环:对value这个char数组每个元素都算个出个和31相关的数。

  • 假设这里value数组的长度为3(value.length = 3),那么逻辑过程就是这样的:

i=0 -> h = 31 * 0 + val[0]
i=1 -> h = 31 * (31 * 0 + val[0]) + val[1]
i=2 -> h = 31 * (31 * (31 * 0 + val[0]) + val[1]) + val[2]

//把括号里的数据乘出来
  h = 31*31*31*0 + 31*31*val[0] + 31*val[1] + val[2]
//于是我们可以推导出这个式子
  h = 31^(3-1)*val[0] + 31^(3-2)*val[1] + val[2]

上面的 for 循环推导出一个计算公式:

val[0]*31^(n-1) + val[1]*31^(n-2) + ... + val[n-1]

上面公式的推导并不是本文的重点,大家了解了解即可。但需要知道这里时时刻刻都有着31的身影。

  • 选择数字31的原因

原因 1:31 可以被编译器优化为31∗i=(i<<5)−i,位运算减法运算效率比乘法运算高。
原因 2: 31 是一个质数:质数是只能被 1 和自身整除的数,使用质数作为乘法因子获得的散列值,在将来进行取模时,得到相同 index 的概率会降低,即降低了哈希冲突的概率
原因 3: 31 是一个不大不小的质数:质数太小容易造成散列值聚集在一个小区间,提供散列冲突概率;质数过大容易造成散列值超出 int 的取值范围(上溢),丢失部分数值信息,散列冲突概率不稳定。

Q: 在Java中如何优化JavaBean的hashCode方法?

在Java中,hashCode方法的优化对于提高散列表(如HashMap、HashSet等)的性能至关重要。以下是一些优化hashCode方法的建议:

  • 一致性:对于同一个对象,无论在任何上下文中调用hashCode()方法,都应返回相同的值。这是hashCode方法的基本要求。
  • 高效性:计算hashCode()的时间复杂度应尽可能低,以便在大量数据中快速查找。
  • 均匀分布:生成的hashCode值应尽量均匀分布在散列表的各个位置,以减少哈希冲突的概率。
  • 避免使用输入字段中的特殊字符或空格:这些字符可能导致hashCode的计算结果不均匀分布。
  • 考虑使用不可变字段:如果对象的某些字段在创建后不会改变,那么可以将这些字段纳入hashCode的计算中。这样,只要对象不变,其hashCode就不会改变,这有助于提高性能。
  • 不要使用输入字段的负值:负值可能导致hashCode的分布不均匀。
  • 考虑使用位操作:位操作通常比乘法和除法更快,可以考虑将多个字段的值通过位操作组合成一个hashCode。
  • 避免使用重量级的计算:如果必须使用复杂的计算,尽量将其放在一个单独的方法中,并在hashCode方法中调用该方法。
  • 注意null值:对于null值,需要决定如何处理。一种常见的做法是返回一个特定的常量值(如0或-1)。
  • 文档和测试:明确文档中说明hashCode方法的实现方式和使用限制,并进行充分的测试以确保其正确性和性能。

X 推荐文献

  • openjdk

https://openjdk.org/
https://openjdk.org/projects/jdk/
https://github.com/openjdk/jdk/

标签:code,hash,String,java,31,value,hashCode,Java
From: https://www.cnblogs.com/johnnyzen/p/18555339

相关文章

  • Java 实际开发中积累的几个小技巧
    一、枚举类的注解看起来很常见的枚举,可能也隐藏着使用上的问题:你有没有在代码里不小心做过改变枚举值的操作?或者为怎么合理规范地写构造方法/成员方法而烦恼?那么不妨来看看我的示例,注释写得比较清楚了:@Getter//只允许对属性get,不允许set@RequiredArgsConstructor//......
  • JavaScript函数式编程指南
    前言本文内容来自于《JavaScript函数式编程指南》,可以看作是对原书内容进行提炼和总结,若您有需要或感觉有出入请参原书。一、走进函数式面向对象编程(OOP)通过封装变化使得代码更易理解。函数式编程(FP)通过最小化变化使得代码更易理解。——MichaelFeathers(Twitter)函......
  • cat-3.1.0 单机搭建监控 java
    官网:https://github.com/dianping/cat/wiki/readme_server下载:https://github.com/dianping/cat/releases/download/3.1.0/cat-home.warhttps://github.com/dianping/cat/archive/refs/tags/3.1.0.tar.gz参考文档:https://github.com/dianping/cat/wiki/readme_server本次......
  • JAVA反序列化学习-CommonsCollections5(基于ysoserial)
    环境准备JDK1.8(8u421)我以本地的JDK8版本为准、commons-collections(3.x4.x均可这里使用3.2版本)cc3.2:<dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2</version>&l......
  • 来记录一下Java开发三年左右的心得体会
    Java开发三年心得大概一年多没有在CSDN上发表文章了,最近休息想来记录一下这几年的开发心得。深圳比亚迪的外包之路~开始北漂~24年被裁一周内又找到工作啦。最后的心得大概一年多没有在CSDN上发表文章了,最近休息想来记录一下这几年的开发心得。2021年大概冬天的时候......
  • java中使用Jackson代替fastjson进行序列化处理
    方法详解这里会列出常用方法的详解,更多方法可查阅jacksonapi文档ObjectMapper类是Jackson库的主要类。它提供一些功能将转换成Java对象匹配JSON结构对象转json字符串ObjectMapper通过writeValue系列方法将java对象序列化为json,并将json存储成不同的格式:String(writeVa......
  • 大学生HTML期末大作业——HTML+CSS+JavaScript南宁绿城
    HTML+CSS+JS【旅游网站】网页设计期末课程大作业web前端开发技术web课程设计网页规划与设计......
  • Java防止反编译的技术方案
    背景由于Java字节码的抽象级别较高,因此它们较容易被反编译。本文介绍了几种常用的方法,用于保护Java字节码不被反编译。通常,这些方法不能够绝对防止程序被反编译,而是加大反编译的难度而已,因为这些方法都有自己的使用环境和弱点。不同保护技术比较表以下几种技术都有不同的应用......
  • JAVA反序列化学习-CommonsCollections4(基于ysoserial)
    环境准备JDK1.8(8u421)这里ysoserial没有提及JDK版本的影响,我以本地的JDK8版本为准、commons-collections4(4.0以ysoserial给的版本为准)、javassist(3.12.1.GA)cc4.0、ClassPool<dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections......
  • python+vue基于django/flask的连锁超市销售管理系统(超市库存与销售管理平台)java+nodej
    目录技术栈和环境说明具体实现截图预期达到的目标系统设计详细视频演示技术路线解决的思路性能/安全/负载方面可行性分析论证python-flask核心代码部分展示python-django核心代码部分展示研究方法感恩大学老师和同学源码获取技术栈和环境说明本系统以Python开发语言......