优化哈希策略的简介

总览

用于哈希键的策略可以直接影响哈希集合(例如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/359331.shtml

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

相关文章

java中报错convension_LambdaConversionException与泛型:JVM错误?

此错误不是完全固定的。我只是LambdaConversionException在1.8.0_72中遇到了一个问题&#xff0c;发现Oracle的错误跟踪系统中有未解决的错误报告&#xff1a; link1&#xff0c;link2。(编辑&#xff1a;据报告&#xff0c;链接的错误已在JDK 9 b93中关闭)作为一种简单的解决方…

java初学者指南_Java初学者指南

java初学者指南Java编程的第一步。 对于Java中的入门教程&#xff0c;请参阅Sun的官方帮助这里 除了核心语言外&#xff0c;还有几种技术和API 介绍。 我们建议首先阅读涵盖 基础知识&#xff0c;并继续其余的教程。 我们建议&#xff1a; 保持代码简单明了且易于阅读 拆…

Java面试题2019简书_2019最新Spring面试题大全含答案之Spring Beans(2019最全Spring超级葵花宝典)...

1.什么是Spring beans&#xff1f;Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化&#xff0c;装配&#xff0c;和管理。这些beans通过容器中配置的元数据创建。比如&#xff0c;以XML文件中 的形式定义。Spring 框架定义的beans都是单件bean…

javascript练习----复选框全选,全不选,反选

第一种方式&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Document</title><style>body{color: #000}</style> <script> document.write("<ul>&quo…

使用此首选项可加快Eclipse m2e配置

谁不认识他们。 Eclipse中的旧式JFace对话框可以使您直观地看到实际上是一个相当简单的XML或属性文件。 对于m2e&#xff0c;它看起来像这样&#xff1a; 不幸的是&#xff0c;此屏幕的加载速度有点慢&#xff0c;除了检查版本号和您将永远不会更改的其他内容之外&#xff0c…

php 判断两个数组差集,php array_udiff_assoc 计算两个数组的差集实例

php array_udiff() 函数用于比较两个(或更多个)数组的键名和键值&#xff0c;并返回差集。此比较是通过用户提供的回调函数来进行的。如果认为第一个参数小于&#xff0c;等于&#xff0c;或大于第二个参数时必须分别返回一个小于零&#xff0c;等于零&#xff0c;或大于零的整…

【百度地图API】发布静态图API啦!只需一个网址,即可展示定制百度地图!

【百度地图API】发布静态图API啦&#xff01;只需一个网址&#xff0c;即可展示定制百度地图&#xff01; 原文:【百度地图API】发布静态图API啦&#xff01;只需一个网址&#xff0c;即可展示定制百度地图&#xff01;摘要&#xff1a; 百度地图静态图API&#xff01;您无须执…

脚本在流程中的性能影响

我们经常看到人们出于各种目的而使用脚本&#xff08;例如&#xff0c;在服务任务&#xff0c;执行侦听器等中&#xff09;。 使用脚本和Java逻辑通常很有意义&#xff1a; 它不需要打包到jar中并放在classpath上 它使流程定义更易于理解&#xff1a;无需查看其他文件 逻辑是…

php 弹出变量,php取变量出现Notice: Undefined variable 的解决方法

Notice: Undefined variable: email in D:\PHP5\ENOTE\ADDNOTE.PHP on line 9Notice: Undefined variable: subject in D:\PHP5\ENOTE\ADDNOTE.PHP on line 9Notice: Undefined variable: comment in D:\PHP5\ENOTE\ADDNOTE.PHP on line 9........本来php是不需要定义变量的&am…

PowerDesigner使用教程

原文&#xff1a;http://www.cnblogs.com/huangcong/archive/2010/06/14/1757957.html PowerDesigner是一款功能非常强大的建模工具软件&#xff0c;足以与Rose比肩&#xff0c;同样是当今最著名的建模软件之一。Rose是专攻UML对象模型的建模工具&#xff0c;之后才向数据库建模…

mac os java_适用于Mac OS X的官方Java 7 –状态

mac os java希望到目前为止&#xff0c;每个人都知道苹果公司去年加入了OpenJDK项目。 那是什么意思&#xff1f; 苹果将​​把用于私有Mac Java构建的代码作为GPL代码贡献给OpenJDK Oracle将接管Java的Mac端口 随着时间的流逝&#xff0c;Mac平台将成为Java世界中完全一流的…

php 鼠标点击图片放大,鼠标移入放大图片预览效果实现

商城项目中&#xff0c;有鼠标移入图片放大的功能&#xff0c;研究一下实现Image zoombody {display: flex;justify-content: center;align-items: center;min-height: 100vh;}#image {width: 300px;height: 300px;background-color: #000;background-image: url(https://place…

红帽峰会2015所需的JBoss BPM内容指南

明年再见&#xff1f; 今年在Red Hat Summit上&#xff0c;我们在JBoss BRMS和JBoss BPM Suite演讲中获得了很多乐趣。 在DevNation周围也有一些社区会议&#xff0c;重点介绍了使我们的产品成为可能的项目。 您可以在他们的博客上找到此演讲的概述&#xff0c;并在其中提供…

设计模式 之 享元

享元模式&#xff08;Flyweight&#xff09; 运用共享技术有效地支持大量细粒度的对象。 还记得那年夏天一起在作文本上玩过的五子棋吗&#xff1f;五子棋是一种两人对弈的纯策略型棋类游戏&#xff0c;它起源于中国古代的传统黑白棋种之中的一个&#xff0c;不…

php制作简单的用户登陆,如何用php代码实现简单的用户登陆以及登陆验证功能

本文主要简单讲述了如何使用php实现简单的用户登陆以及登陆验证效果。1、首先实现通过Session实现用户的登录那么什么是session呢&#xff1f;session被译为会话&#xff0c;它主要是为了在一定访问期间在不同页面之间间传输数据&#xff0c;用来解决http协议无状态。session在…

纪事日记–可自定义的数据存储

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

php vprintf,vprintf - [ C语言中文开发手册 ] - 在线原生手册 - php中文网

格式-指向以空字符结尾的字符串的指针&#xff0c;指定如何解释数据。格式字符串由普通的多字节字符(&#xff05;除外)组成&#xff0c;它们被原样复制到输出流和转换规范中。每个转换规范具有以下格式&#xff1a;介绍&#xff05;字符(可选)一个或多个标志&#xff0c;用于修…

Quick cocos2dx-Lua(V3.3R1)学习笔记(十)-----搭建安卓打包环境,用官方示例anysdk生成apk运行...

话说我这一篇就写搭建打包安卓环境&#xff0c;是不是有点过早了&#xff08;其实我主要是怕以后重装系统&#xff0c;忘了怎么搭建了&#xff09;&#xff0c;但是迟早要面对的痛苦&#xff0c;一直延后也不是办法&#xff0c;对吧。 在官方文档中&#xff0c;对于打包安卓apk…

ejb运行程序_EJB程序化查找

ejb运行程序在上一篇文章中&#xff0c;我们了解了EJB 引用和EJB 注入 。 尽管EJB注入是一种强大的容器工具&#xff0c;可以简化模块化应用程序的开发&#xff0c;但有时还是希望执行程序化EJB查找。 例如&#xff0c;让我们假设一组不同的EJB实现由公共业务接口定义的公共策略…

php实现返回顶部,返回顶部js

返回顶部jsfunction goTopEx(){var objdocument.getElementById(“goTopBtn”);function getScrollTop(){return (document.documentElement.scrollTopdocument.body.scrollTop);}function setScrollTop(value){if(document.documentElement && document.documentEleme…