哈希策略_优化哈希策略的简介

哈希策略

总览

用于哈希键的策略可以直接影响哈希集合(例如HashMap或HashSet)的性能。

内置的哈希函数被设计为通用的,并且可以在各种用例中很好地工作。 我们可以做得更好,特别是如果您对用例有一个很好的了解吗?

测试哈希策略

在上一篇文章中,我介绍了多种测试哈希策略的方法,特别是针对“正交位”进行了优化的哈希策略,旨在确保每个哈希结果仅基于一个比特就尽可能地不同变化。

但是,如果您有一组已知的要散列的元素/键,则可以针对该特定用例进行优化,而不是尝试查找通用解决方案。

减少碰撞

您想要避免在哈希集合中发生的主要事情之一是冲突。 这是两个或更多键映射到同一存储桶时。 这些冲突意味着您必须做更多的工作来检查密钥是否与您期望的一致,因为同一存储桶中现在有多个密钥。 理想情况下,每个存储桶中最多有一个密钥。

我只需要唯一的哈希码,不是吗?

一个常见的误解是,为了避免冲突,您需要拥有唯一的哈希码。 尽管非常需要唯一的哈希码,但这还不够。

假设您有一组密钥,并且所有密钥都有唯一的32位哈希码。 如果您有一个包含40亿个存储桶的数组,则每个键都有其自己的存储桶,并且不会发生冲突。 对于所有散列集合,通常不希望具有如此大的数组。 实际上,对于2 ^ 30或刚刚超过10亿的数组,HashMap和HashSet受2大小的最大幂限制。

当您拥有更实际大小的哈希集合时,会发生什么? 存储桶的数量需要更小,并且哈希码被模块化为存储桶的数量。 如果存储桶数是2的幂,则可以使用最低位的掩码。

让我们来看一个示例ftse350.csv。如果将第一列作为键或元素,则将获得352个字符串。 这些字符串具有唯一的String.hashCode(),但是说我们采用了这些哈希码的低位。 我们看到碰撞了吗?

面具 屏蔽了String.hashCode() HashMap.hash(
屏蔽了String.hashCode())
32位 没有碰撞 没有碰撞
16位 1次碰撞 3次碰撞
15位 2次碰撞 4次碰撞
14位 6次碰撞 6次碰撞
13位 11次碰撞 9次碰撞
12位 17次碰撞 15次碰撞
11位 29次碰撞 25次碰撞
10位 57次碰撞 50次碰撞
9位 103次碰撞 92次碰撞


HashMap的加载因子为0.7(默认值)的大小为512,它使用低9位的掩码。 如您所见,即使我们从唯一的哈希码开始,仍有大约30%的键发生冲突。

  • HashTesterMain的代码在这里。

为了减少不良的哈希策略的影响,HashMap使用了一种搅拌函数。 在Java 8中,这非常简单。

从HashMap.hash的源代码中,您可以阅读Javadoc以获得更多详细信息

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

这将哈希码的高位与低位混合,以改善低位的随机性。 对于上述高碰撞率的情况,存在改进。 请参阅第三列。

看一下String的哈希函数

String.hashCode()的代码

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;
}

注意: String的实现是在Javadoc中定义的,因此几乎没有机会更改它,但是可以定义新的哈希策略。

哈希策略的组成部分。

在散列策略中,我要看两部分。

  • 魔术数字。 您可以尝试不同的数字以找到最佳结果。
  • 代码的结构。 您需要一种结构,对于任何理智的幻数选择都能获得良好的结果。

尽管幻数很重要,但您不希望它们太重要的原因是,对于给定的用例,您总是有可能选择不正确的幻数。 这就是为什么您还想要一种即使在选择的魔术数不多的情况下也能获得最差的情况下的结果的代码结构。

让我们尝试一些不同的乘数,而不是31。

乘数 碰撞
1个
230
2
167
3
113
4
99
5
105
6
102
7
93
8
90
9
100
10
91
11
91


您会看到魔术数字的选择很重要,但是也有很多数字可供尝试。 我们需要编写一个测试来尝试一个好的随机选择。 HashSearchMain的来源

哈希函数 最佳乘数 最低的碰撞 最差的乘数 最高碰撞
hash()
130795
81次碰撞
126975
250次碰撞
xorShift16(哈希())
2104137237
68次碰撞
-1207975937
237次碰撞
addShift16(hash())
805603055
68次碰撞
-1040130049
243次碰撞
xorShift16n9(hash())
841248317
69次碰撞
467648511
177次碰撞


要查看的关键代码是

public static int hash(String s, int multiplier) {int h = 0;for (int i = 0; i < s.length(); i++) {h = multiplier * h + s.charAt(i);}return h;
}private static int xorShift16(int hash) {return hash ^ (hash >> 16);
}private static int addShift16(int hash) {return hash + (hash >> 16);
}private static int xorShift16n9(int hash) {hash ^= (hash >>> 16);hash ^= (hash >>> 9);return hash;
}

如您所见,如果您提供了一个良好的乘数,或者一个乘数恰好与您的键集配合使用,则每个哈希加下一个字符的重复乘数是合理的。 如果将130795作为乘数而不是31作为乘数,则对于测试的密钥集,您只会得到81次碰撞,而不是103次碰撞。

如果同时使用搅拌功能,则可能发生68次碰撞。 这接近两倍于数组大小的相同冲突率。 也就是说,无需使用更多内存即可提高碰撞率。

但是,当我们向哈希集合添加新密钥时会发生什么,我们的幻数仍然对我们有利吗? 在这里,我着眼于最差的碰撞率,以确定在更大范围的可能输入下哪种结构可能产生良好的结果。 hash()的最坏情况是250次冲突,即70%的键碰撞,这是非常糟糕的。 搅动功能对此有所改善,但是仍然不是很好。 注意:如果我们添加移位后的值而不是对其进行异或,则在这种情况下将得到更差的结果。

但是,如果我们进行两次移位,不仅要混合最高位和最低位,还要混合生成的哈希码的四个不同部分的位,则我们发现最坏情况下的冲突率要低得多。 这向我表明,如果更改键的选择,则由于结构更好且魔术数字的选择或输入的选择的重要性降低,我们不太可能收到不好的结果。

如果我们在哈希函数中添加而不是xor怎么办?

在搅拌功能中,使用xor可能比使用add更好。 如果我们改变这个会发生什么

h = multiplier * h + s.charAt(i);

h = multiplier * h ^ s.charAt(i);
哈希函数 最佳乘数 最低的碰撞 最差分数 最高碰撞
hash()
1724087
78次碰撞
247297
285次碰撞
xorShift16(哈希())
701377257
68次碰撞
-369082367
271次碰撞
addShift16(hash())
-1537823509
67次碰撞
-1409310719
290次碰撞
xorShift16n9(hash())
1638982843
68次碰撞
1210040321
206次碰撞


最佳情况下的数字稍好一些,但是最坏情况下的碰撞率则更高。 这向我表明,幻数的选择更重要,但这也意味着键的选择更重要。 这似乎是一个冒险的选择,因为您必须考虑密钥可能会随着时间而变化。

为什么我们选择奇数乘数?

当您乘以奇数时,结果的低位机会等于0或1。这是因为0 * 1 = 0和1 * 1 =1。但是,如果将您乘以偶数,则低位总是为0。即不再是随机的。 假设我们重复了先前的测试,但只使用了偶数,这看起来如何?

哈希函数 最佳乘数 最低的碰撞 最差分数 最高碰撞
hash()
82598
81次碰撞
290816
325次碰撞
xorShift16(哈希())
1294373564
68次碰撞
1912651776
301次碰撞
addShift16(hash())
448521724
69次碰撞
872472576
306次碰撞
xorShift16n9(hash())
1159351160
66次碰撞
721551872
212次碰撞


如果您很幸运,并且为您的魔术数字输入了正确的结果,则结果与奇数一样好,但是,如果您很不幸,结果可能会很糟糕。 325次碰撞意味着仅使用了512个铲斗中的27个。

更多高级哈希策略有何不同?

对于基于City,Murmur,XXHash和Vanilla Hash(我们自己的)的哈希策略

  • 散列策略一次读取64位数据的速度比逐字节读取数据的速度快。
  • 计算的工作值是两个64位值。
  • 工作值减少到64位长。
  • 结果,使用了更多的乘法常数。
  • 搅拌功能更为复杂。

我们在实现中使用长哈希码为:

  • 我们针对64位处理器进行了优化,
  • Java中最长的原始数据类型是64位,并且
  • 如果您有大量的哈希集合(即数百万个),则32位哈希不太可能是唯一的。

综上所述

通过探索如何生成哈希码,我们找到了将352个键的冲突次数从103个冲突减少到68个冲突的方法,但是比更改键集有一定的信心,我们已经减少了这种影响。

这无需使用更多的内存,甚至不需要更多的处理能力。
我们仍然可以选择使用更多的内存。

为了进行比较,您可以看到将数组的大小加倍可以改善最佳情况,但是仍然存在一个问题,即密钥集和幻数之间的未命中匹配仍然会具有较高的冲突率。

哈希函数 最佳乘数 最低的碰撞 最差分数 最高碰撞
hash()
2924091
37次碰撞
117759
250次碰撞
xorShift16(哈希())
543157075
25次碰撞
– 469729279
237次碰撞
addShift16(hash())
-1843751569
25次碰撞
– 1501097607
205次碰撞
xorShift16n9(hash())
-2109862879
27次碰撞
-2082455553
172次碰撞

结论

在具有稳定密钥集的情况下,可以通过调整使用的哈希策略来显着提高冲突率。 您还需要进行测试,这些测试表明如果密钥集未经重新优化就可能变坏。 结合使用这两种方法,您可以开发新的哈希策略来提高性能,而不必使用更多的内存或更多的CPU。

翻译自: https://www.javacodegeeks.com/2015/09/an-introduction-to-optimising-a-hashing-strategy.html

哈希策略

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

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

相关文章

面试大全 | C语言高级部分总结,2.6万字长文

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删一、内存大话题1.0、内存就是程序的立足之地&#xff0c;体现内存重要性。1.1、内存理解&#xff1a;内存物理看是有很多个 Bank&#xff08;就是…

c#设计12星座速配软件_C#设计模式(12)——组合模式

阅读目录1.组合模式在软件开发中我们经常会遇到处理部分与整体的情况&#xff0c;如我们经常见到的树形菜单&#xff0c;一个菜单项的子节点可以指向具体的内容&#xff0c;也可以是子菜单。类似的情况还有文件夹&#xff0c;文件夹的下级可以是文件夹也可以是文件。举一个例子…

hibernate与jpa_将JPA Hibernate与OptaPlanner集成

hibernate与jpa我们一直在改进OptaPlanner与JEE其余部分的集成&#xff0c;因此更容易构建可以正常工作的最终用户应用程序。 让我们看一下改进的JPA Hibernate集成。 基础 JPA Hibernate和OptaPlanner都可以在POJO&#xff08;普通的旧Java对象&#xff09;上工作&#xff0c…

程序如何运行,编译、链接、装入?

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删一、地址概念和程序如何运行在多道程序环境下&#xff0c;要使程序运行&#xff0c;必须先为之创建进程。而创建进程的第一件事&#xff0c;便是…

python举两种字符串引号的例子_python里的单引号和双引号的有什么作用

在Python当中表达字符串既可以使用单引号&#xff0c;也可以使用双引号&#xff0c;那两者有什么区别吗&#xff1f;python单引号和双引号的区别简单来说&#xff0c;在Python中使用单引号或双引号是没有区别的&#xff0c;都可以用来表示一个字符串。但是这两种通用的表达方式…

枚举对象注释_如何以及何时使用枚举和注释

枚举对象注释本文是我们名为“ 高级Java ”的学院课程的一部分。 本课程旨在帮助您最有效地使用Java。 它讨论了高级主题&#xff0c;包括对象创建&#xff0c;并发&#xff0c;序列化&#xff0c;反射等。 它将指导您完成Java掌握的旅程&#xff01; 在这里查看 &#xff01;…

background 互联网图片_cssbackground-image和layer-background-image的区别

layer-background-image语法&#xff1a;layer-background-image : none | url (url)参数&#xff1a;none :  无背景图url :  使用绝对或相对地址指定背景图像说明&#xff1a;设置或检索对象整个区域的背景图像。示例&#xff1a;code {position: absolute;top: 100px; lef…

纪事本 乱码_纪事日记–可自定义的数据存储

纪事本 乱码总览 使任何数据结构或算法尽可能快的方法是使代码完全执行您想要的操作&#xff0c;而无需执行其他操作。 建立一个可以做任何人想做的每件事的数据存储的问题是&#xff0c;它做得特别不好。 自定义数据存储在性能方面可以实现什么&#xff1f; 您可以支持&#…

datavideo切换台说明书_【新品发布】datavideo SE-650 高清四通道切换台

还在为音乐演唱会的拍摄而烦恼吗&#xff1f;或者为体育比赛的多机位发愁&#xff1f;或者为微课、优课、慕课制作而焦头烂额&#xff1f;大部分用户对多机位的恐惧来源于相关产品令人发指的复杂和专业性&#xff0c;面对满眼的键盘会有无从下手之感&#xff0c;很多学校和工作…

NSA:建议从 C/C++ 切换到内存安全语言

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删美国国家安全局&#xff08;NSA&#xff09;发布了一份指南&#xff0c;旨在帮助软件开发商和运营商预防和缓解软件内存安全问题。其鼓励组织将编…

探索cqrs和事件源_编写基于事件的CQRS读取模型

探索cqrs和事件源关于事件源和CQRS的讨论似乎通常集中在CQRS上下文中的整体系统架构或领域驱动设计的各种形式。 但是&#xff0c;尽管也有一些有趣的考虑&#xff0c;但读取模型经常被忽略。 在本文中&#xff0c;我们将介绍通过使用事件流来填充视图模型的示例实现。 总览 …

shmmax单位_kernel.shmmax ,kernel.shmmni 和kernel.shmall

kernel.shmmax 2147483648//该参数定义了共享内存段的最大尺寸(以字节为单位)。其值应>sag_max_size初始化参数的大小,否则SAG由多个内存段构成,效率降低,还要不小于物理内存的一半,默认情况下在32位x86系统中,OracleSGA最大不能超过1.7GB.缺省为32M&#xff0c;对于oracle…

C# 11正式发布

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删C# 11 现已发布。公告称&#xff0c;“随着每个版本的发布&#xff0c;社区的参与度越来越高&#xff0c;贡献了从建议、见解和错误报告到整个功…

java 自定义运算符_Java中集合的自定义运算符

java 自定义运算符总览 操作员重载有多种语言可用。 Java对String类型的运算符的支持对运算符的重载非常有限。 我们可以利用其他语言支持运算符的不同方式&#xff0c;但是我们可以在Java中实现一个使用Java已经使用的约定的实现。 获取&#xff0c;设置和放置操作 集合的运…

marker主题 ros_(五)ROS主题理解

参考网址&#xff1a;1&#xff0c;小海龟例子(1) 在新的终端打开roscore$ roscore&#xff0d;&#xff0d;&#xff0d;如果出错&#xff0c;请确定关闭所有ROS命令或者路径&#xff0c;重试。(2) 在新的终端打开运行小海龟界面$ rosrun turtlesimturtlesim_node得到结果&…

分享一个通用的嵌入式驱动层

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删C 语言面向对象编程的最佳实践一、前言以STM32为例&#xff0c;打开网络上下载的例程或者是购买开发板自带的例程&#xff0c;都会发现应用层中会…

cks32和stm32_cks子,间谍,局部Mo子和短管

cks32和stm32本文是我们名为“ 用Mockito测试 ”的学院课程的一部分。 在本课程中&#xff0c;您将深入了解Mockito的魔力。 您将了解有关“模拟”&#xff0c;“间谍”和“部分模拟”的信息&#xff0c;以及它们相应的存根行为。 您还将看到使用测试双打和对象匹配器进行验证…

CSON+CJSON,解析json数据更优雅?

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删前言json是目前最为流行的文本数据传输格式&#xff0c;特别是在网络通信上广泛应用&#xff0c;随着物联网的兴起&#xff0c;在嵌入式设备上&a…

服务总线yali测试_服务器的压力测试方法与流程

本发明涉及服务器的测试技术领域&#xff0c;特别是涉及一种包括超路径互联总线的服务器的压力测试方法。技术背景QPI(Quick Path Interconnect&#xff0c;快速通道互联)总线技术是一种取代前端总线(FSB)的CPU(处理器)间点对点连接技术。英特尔公司在2017年发起UPI(Ultra Path…

deprecated_@Deprecated新外观可能是什么?

deprecatedJDK增强建议 &#xff08;JEP&#xff09;277&#xff08;“ 增强的弃用 ”&#xff09;建议“重新定义弃用注释&#xff0c;并提供加强功能生命周期尾端的工具。” 当前 java.lang.Deprecated的一些限制使我困扰了一段时间。 我特别希望能够使用Deprecated提供文本&…