Java 字符串 HashCode 的奇怪情况

news2024/11/20 21:32:52

过去 1.5 年以来,我一直在使用 Java 编程。最近,我在尝试对 Java 数据结构进行性能分析。为了亲自体验一下,我决定玩一下我最喜欢的数据结构,即 HashSet。HashSet 提供 O(1) 查找和插入时间。我测量并比较了在 HashSet 中查找具有不同大小的随机字符串所需的时间。

以下是我编写的代码片段:-

public class HashCodePerformance {
    public static void main(String[] args) {
        Set<String> stringHashSet = new HashSet<>();
        stringHashSet.add("London");
        stringHashSet.add("Mumbai");
        stringHashSet.add("NewYork");
        List<String> stringsToSearch = Arrays.asList("f5a5a608", "48abre7a6 i8a5r507",
                "7e50bc488 pl43fvf1p 65", "e843r6f1p vfvdfv vdvdg vgbgd ", "38aeaf9a6");
        for (String string : stringsToSearch) {
            Stopwatch timer = Stopwatch.createStarted();
            for (int index=0; index < 10000000; ++index) {
                stringHashSet.contains(string);
            }
            System.out.println("Search String \"" + string + "\" time taken " + timer.stop());
        }
    }
}

//输出
Search String "f5a5a608" time taken 94.51 ms
Search String "48abre7a6 i8a5r507" time taken 37.79 ms
Search String "7e50bc488 pl43fvf1p 65" time taken 28.29 ms
Search String "e843r6f1p vfvdfv vdvdg vgbgd " time taken 26.46 ms
Search String "38aeaf9a6" time taken 80.07 ms

从输出结果我们发现了一个有趣的现象。第一个和最后一个字符串查找所花的时间几乎是中间三个字符串的 3 到 4 倍。即使中间三个字符串的长度更长,

我们发现了一个有趣的现象。第一个和最后一个字符串查找(以红色突出显示)所花的时间几乎是中间三个字符串的 3 到 4 倍。即使中间三个字符串的长度更长,但查找效率却更高。这意味着HashSet查找与字符串的长度无关。

为了理解 HashSet 的这种不寻常的行为,让我们回到基础并了解基本原理。

HashSet 内部工作原理

Java HashSet 内部使用链表数组来执行 O(1) 插入、查找和删除。HashSet 首先计算对象的哈希值,以确定对象将存储在数组的哪个索引处。然后,将对象存储在计算出的索引处。同样的原理适用于查找和删除。

访问数组元素是 O(1)时间复杂度,所以唯一的开销是计算对象的哈希值。因此,哈希函数需要是最佳的,以避免任何性能影响。

此外,哈希函数的输出应该具有均匀分布。如果发生冲突,给定索引处的链表长度将不断增长,最坏情况复杂度将变为 O(n)。

HashCode 设计

在 Java 中,每个对象都有一个 hashCode() 函数。HashSet 调用此函数来确定对象索引。让我们回顾一下我们分析字符串查找性能的示例,看看随机字符串的hashCode值。

我们可以看到异常字符串的 hashCode 为 0。现在,是时候深入研究一些代码并查看实现情况了。

在旧版本的JDK 1.0+和1.1+中,字符串的hashCode函数对每个第n个字符进行采样。这种方法的缺点是许多字符串映射到相同的哈希,导致冲突。

在 Java 1.2 中,使用下面的算法。这个算法有点慢,但有助于避免碰撞。

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 时,变量hash的默认值为 0,并执行第 3-9 行。后续调用 hashCode() 时,如果 hash 非零,则不会执行第 3-9 行。

可以推断,hashCode() 函数使用了一种缓存方法,其中仅在第一次调用时计算哈希值,之后的调用将获得相同的计算值。

如果字符串的哈希值为 0,则每次调用该函数时都会进行哈希计算。现在,就应该清楚为什么查找一些字符串比其他字符串花费更多时间了。

克服性能损失

对于哈希值为 0 的字符串,上述 HashCode 计算性能较差。我们如何优化它?

任何从事计算机软件开发的程序员都应该知道使用布尔标志,该标志将在第一次计算后设置,并会在后续调用中跳过计算。

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

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

可以看出优化并不难,那为什么 Java 开发人员一开始没有想到这种优化,或者为什么在 Java 的后续版本中没有修补这个问题?

为什么不修复HashCode?

根据实现,以下是任何具有“n”个字符的字符串的哈希公式。

hash = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

这里 s[n] 是字符串中的第 n 个字符

这个哈希函数在整数范围内提供了均匀分布。这意味着字符串哈希为0的概率是1/2^32。

我们可以想到以下情况,其中字符串的哈希为零:

  • 字符串仅包含 0(Unicode 字符)
  • 空字符串
  • 由于整数溢出,哈希码为 0

目前,只有哈希值为 0 的字符串会受到影响。而在实际应用中,这几种字符串出现的概率很小。假设我们通过添加布尔值来修复 hashCode。总体而言,我们不会看到对实际系统性能产生任何巨大提升。它可能会导致速度提高 0.000010%。这类似于说我们将一个可以在1小时完成的任务优化为59分钟59秒7毫秒。

因此,这就是为什么在 Java 的后续版本中没有修补这个问题的原因。

哈希值为 0 的英文字符串

我拿了一个包含20k英文单词的词典列表,并尝试组合这些单词以检查它们的哈希是否为零。当我考虑单个有意义的英文单词时,没有一个哈希为零。两个或多个单词的组合会产生零哈希值。

以下是一些哈希为零的句子(有意义的单词)的例子:

  • carcinomas motorists high
  • zipped daydreams thunderflashes
  • where inattentive agronomy
  • drumwood boulderhead

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

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

相关文章

kafka高性能的底层原理分析

目录 1.磁盘顺序写 2.零拷贝 3.数据压缩 4.消息批量处理 5.pageCache 6.稀疏索引 总结 Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff0c;它可以处理消费者在网站中的所有动作流数据。那么他是如何做到高性能的呢&#xff0c;本篇文章从宏观上分析一下&#xff…

论文阅读——Design of Environmental backscatter tag antenna for 5G Internet of things

文章目录 摘要一、背景二、系统模型三、天线设计A. 指标B. 天线结构描述C. 天线结构优化D. 天线结构确定 四、仿真结果总结 论文来源&#xff1a;https://ieeexplore.ieee.org/document/9379395 摘要 文章针对传统设备识别在电力物联网场景中存在的可靠性低和读取距离不足的问…

2024年陕西省职业院校技能大赛高职信息安全管理与评估样题

2024年陕西省职业院校技能大赛高职信息安全管理与评估样题 模块一竞赛项目试题 根据信息安全管理与评估技术文件要求&#xff0c;模块一为网络平台搭建与网络安全防护。本文件为信息安全管理与评估项目竞赛-模块一试题。 模块一 平台搭建与安全设备防护 竞赛任务 网络平台搭建…

[网鼎杯 2020 朱雀组]Nmap(详细解读版)

这道题考察nmap的一些用法,以及escapeshellarg和escapeshellcmd两个函数的绕过&#xff0c;可以看这里PHP escapeshellarg()escapeshellcmd() 之殇 (seebug.org) 两种解题方法&#xff1a; 第一种通过nmap的-iL参数读取扫描一个文件到指定文件中第二种是利用nmap的参数写入we…

CAN总线的错误检测机制

文章目录 错误类型1、位错误-Bit Check Error2、填充错误-Stuff Error3、格式错误-Form Error4、ACK错误-ACK Error 和 CRC错误-CRC Error5、错误示例 错误处理1、主动错误 - error active2、被动错误 - error passive3、总线关闭 - bus off4、错误计数值的变动条件 参考 错误类…

信创终端操作系统上vmware的命令行操作

原文链接&#xff1a;信创终端操作系统上vmware的命令行操作 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在信创终端操作系统上使用命令行操作VMware的文章。通过命令行管理VMware虚拟机可以提高效率&#xff0c;特别是在需要批量操作或自动化管理时。本文将…

【PyCharm】PyCharm 2024.1 的最新变化-代码导航与重构

目录 代码导航与重构 改进的代码导航功能 高级重构工具 代码导航与重构 改进的代码导航功能 符号搜索和文件搜索增强&#xff1a;改进后的符号搜索功能能够更快速地定位到特定的类、函数、变量等&#xff0c;而增强的文件搜索则让用户能够轻松找到所需的文件或目录。现在&a…

【IJER】: 二苄基甲苯加氢及不同 Pt 负载量的 Pt/Al对全氢二苄基甲苯制氢的催化性能

摘要&#xff1a; 研究了二苄基甲苯加氢和全氢二苄基甲苯(H18-DBT)脱氢的特性。氢化实验使用Raney-Ni进行。结果表明&#xff0c;170℃、7MPa是最佳反应条件&#xff0c;在30小时内氢气容量达到6.2wt%。 H18-DBT 脱氢的最佳温度为 290C。此外&#xff0c;在不同Pt金属负载量&am…

【Redis系列】RedisTemplate的使用与注意事项

目录 一.什么是RedisTemplate 二.如何使用RedisTemplate RedisTemplate的API 序列化 三.StringRedisTemplate 一.什么是RedisTemplate RedisTemplate 是一个工具类&#xff0c;由 Spring 官方提供的方便操作 Redis 数据库的一个工具类&#xff0c;来源于 org.springframe…

day3 测试基础知识

1. 你认为性能测试的目的是什么&#xff1f;做好性能测试的工作的关键是什么&#xff1f; 性能测试工作的目的是检查系统是否满足在需求说明书中规定的性能&#xff0c;性能测试常常需要和强度测试结合起来&#xff0c;并常常要求同时进行软件和硬件的检测。 性能测试主要的关…

写在礼拜天

这两天巴黎奥运会&#xff0c;办的如火如荼&#xff0c;各个国家都有拿到金牌的人…… 前几天&#xff0c;俞敏洪正式发文&#xff0c;说董宇辉从东方甄选离职&#xff0c;明确已经出来单干…… 前段时间的高考成绩已经揭晓&#xff0c;有人金榜题名&#xff0c;有人名落孙山…

Spring Boot配置文件的语法规则

主要介绍两种配置文件的语法和格式&#xff0c;properties和yml 目录 1.配置文件的作用 2.创建配置文件 3.properties语法 4.yml语法 5.配置文件格式 1.配置文件的作用 对于配置文件&#xff0c;也有独立的文件夹去存放&#xff0c;主要用来存放一些需要经过变动的数据&a…

质数差列 信友队

题目描述 驰骋宇宙的鱼大大找到了一个古遗迹&#xff0c;稍作研究后发现这是一个来着远古的质数星球文明遗迹&#xff0c;这个文明的特点是所有事物都和质数息息相关。于是&#xff0c;鱼大大赶紧列出了一堆的质数&#xff0c;以方便自己的研究。 这天鱼大大找到了质数星球文明…

ubuntu安装docker和docker-compose

安装软件包 sudo apt install apt-transport-https ca-certificates software-properties-common 将 Docker 的 APT 源添加到系统中 echo "deb https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/…

<Python><paddle>基于python使用百度paddleocr实现车牌识别

前言 paddleocr是百度飞桨的一个文字识别库&#xff0c;准确度非常高&#xff0c;基于其文字识别的基础&#xff0c;将其用于车牌识别。这个识别的准确度是相当高的。 环境配置 系统&#xff1a;windows 平台&#xff1a;visual studio code 语言&#xff1a;python 库&#…

FreeModbus学习——eMBInit初始化

FreeModbus版本&#xff1a;1.6 在mb.c文件中 先看一下静态变量的定义 /* ----------------------- Static variables ---------------------------------*/static UCHAR ucMBAddress; static eMBMode eMBCurrentMode;ucMBAddress是从机地址&#xff0c;eMBCurrentMode是M…

简易版:在 SpringBoot 中设计一个订单号生成系统

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 引言 要在SpringBoot中设计一个订单号生成系统&#xff0c;你可以按照以下步骤进行&#xff1a; 创建一个SpringBoot项目&#xff0c;添加必要的依赖&#xff0c;如spring-boot-starter-web。 创建一个订单…

前端了解到框架-网络复习

前端 HTML 超文本标记语言 画页面 各种各样的标签组成页面进行展示 桌面创建文本修改后缀即可 <!DOCTYPE html>: 声明文档类型和HTML版本。<html>: 根标签&#xff0c;所有其他标签都包含在内。<head>: 包含了文档的元数据&#xff0c;如字符编码、网页标…

Mac系统中用brew安装MongoDB【详细教程】

文章目录 概述一、安装步骤1.下载适用于 MongoDB 的官方 Homebrew 公式和数据库工具,方法是在 macOS 终端运行以下命令:2.更新 Homebrew3.安装 MongoDB二、使用步骤1.查看版本2.启动服务3.停止服务4.连接测试三、可视化工具1.MongoDB Compass2.使用概述 使用本教程在 macOS …

heic怎么转换成jpg?heic转jpg,分享6款图片格式转换器免费汇总!

众所周知&#xff0c;在与非苹果手机设备用户&#xff08;如安卓手机或Windows台式机用户&#xff09;分享照片之前&#xff0c;通常需要将iphone的heic格式转换为jpg。由于这些操作系统的旧版本不原生支持heic图片格式&#xff0c;因此需要额外的第三方工具来查看这些图像。因…