Hash表从了解到深入(浅谈)
· Hasn表,将一个数据进行Value化,再进行一个映射关系到Key直接进行访问的一个数据结构,这样可以通过直接的计算进行数据的访问和插入。关于Hash表的基本概念这里就不一一叙述,可以通过百度了解Hash的一些基本概念。今天这里主要讲2个点,Hash冲突与Hash构建函数算法。
1,一个基本的Hash表是什么?
很多人如果只是简单了解过Hash表的结构,可能提到这个数据结构的第一个印象是Hash是由一个数组和一个链表(链表这里可能有很多种形态)组成的数据结构。大错特错,个人感觉这完全是一种错误的植入方式,首先Hash本身的描述仅仅只是一个数组,我们根据每次要插入或者访问的数据,通过Value->Key的一个映射,通过Key直接访问数组下标进行数据插入或访问,在插入数据时,如果当前下标已经放入了某个数值,则在这个下标的基础上向后移动,直到某一个块还没有被插入数据,就将当前需要插入的数据放入当前块。而之所以很多不太了解Hash表的人将其说成一个数组加一个链表,是因为这种数据结构是采用Hash的拉链法进行一个解决Hash冲突的方法而已。
2,什么是Hash冲突?
Hash冲突简单来说就是两个数据,虽然Value不相同(某些存储也会需要存入相同的Value),但通过Hash映射函数所得到的Key是相同的,这两个数据都要存入Hash表中时,就会产生冲突。而我们要解决冲突,就需要拉链法(常用且简单)或一些其他的方法,进行冲突解决,如果Key相同,拉链法是在当前哈希表(数组)存放一个链表的头,若两个数据的Key均相同,则将数据分别存放在链表中。
3,常见解决冲突的方法?
常见解决除了上述的拉链法外,还有一些比较常用的方法像“开放定址法”,“再哈希法”,“建立公共溢出区”。这三种方法的实现百度就有很多,在此处不详细介绍,个人比较喜欢拉链法,也是比较容易实现的一种,就是拉下去会增加空间的开销。
4,装载因子是什么?
装载因子 = 添加的数据总数 / 哈希表的大小,称为饱和程度。一般来说饱和程度越小,对于我们Hash搜索的效率也是越高的,但同样,这就代表这空间的代价也是很高的,往往很多空间是浪费掉没有使用的。比如装载因子为0.2,就表示哈希表只有20%的空间被使用,其余浪费。所以为了平衡这个点,大部分采用0.75作为装载因子,如果超过,就动态进行哈希的重构,增加哈希表的大小。
5,哈希表的大小怎么设定最好?
在开始构造哈希表的时候,由于哈希表的大小是固定的,虽然可以动态调整,但重构起来是比较浪费时间的,所以一般在开始需要根据实际项目情况定好一个大小值。至于大小取什么比较合适,网上有一种说法是取质数,个人理解这种取质数的方法仅仅适用于 Key = Value % Buff 。先讨论一下这种情况,算法导论中有提到,针对于取余这种情况,可以选择远离2的幂次方的质数,其次是普通质数,最后才是刚好为2^幂次数。但在我看来,这完全是一种错误指向。举个例子:为什么最差的宣发是2^幂次,假设对4/8/16等等取余为1,那么对2取余同样是1,这样的一个理解特别容易使人进入一个误区,觉得只要是2^幂次数大小,就会造成不均匀。看似有点道理,但用概率论的想法去思考这个问题,感觉完全是因果没有任何关系,牛唇不对马嘴。
因为随着哈希表大小的增加,取余的范围也会增加,对于任意常熟对其取余,所得到的概率应该均是相等才对,比如1-100的自然数,无论取余多少,都会是类似于桶的一种塞法,每个桶先塞一个,轮询完后回来从第一个继续塞,所以个人认为无论对于取余这种情况,哈希大小选定只要是某个区间内是合适的,就应该是可以的。不在乎是否为2^幂次还是质数(有特殊规律的数据群,需要依靠其规律设计大小)。这一段仅仅时候从常熟概率相同的角度分析,看来是很容易分析到的一种现象,但同样,后面通过一些公式,我们又能证明出另一种可能性。
一个数对一个2^幂次取余其实就是 Value % Key = Value & (Key - 1),所以也就是说这个余数只是Value二进制中的后几位而已,若后几位相同的情况下,前面的位数无论取何值,结果都是相同的,这样产生冲突的可能性就大了一些。
综上所述,如果是已经知道了是一个自然数均匀分布的数据群,那无论取质数或是2^幂次数都是没什么影响的,但如果是某些特殊群,比如后几位或前几位经常会出现相同值,那我们使用远离2^次幂的质数大小就能有效的减少冲突的产生。
(这段讲的真的很乱,就是转折来转折去,最终结论还是建议可以遵守远离2^次幂的质数大小)。
6,Hash构建函数-常见的?
第一个问题提到的插入方法就是最基本的构建方法,直接寻址法,百度中也有一些基本的Hash构造方法,比如:
⑴ 数字分析法:先对可能插入的数据进行简单的分析,比如是数字,那就看有几位,那几位基本是固定的,那我们就用剩下位数的数字作为Key来存放数据。举个例子,比如存放出生日期,那年月日中3个参数中,年份这个参数前两个参数的数据基本是一致的,那就可以直接抛弃,只以十位和个位的数来作为Key进行判断。
⑵ 除留余数法:此方法为 Key = Value % Buff 进行计算的,总体来说这个简单的构建方法也是我比较喜欢的,如果不是做一些大项目,仅仅用于小的数据存储的话,完全可以使用这种构建方法进行Hash构建。
7,好一些的Hash构建算法?
在这个大数据横飞的时代,如果仅仅使用一些简单的Hash构造那肯定是不能满足我们的要求,所以前人们就研究出了许多适用于各种环境下的Hash构造算法,都是很高效,并且现在的项目也在使用的一些算法。
⑴ BKDRHash构造:
BKDR算法,主要应用场景是对字符串数据进行Hash构造的方法,我们先抛开算法一说,从最初开始想办法。首先要将字符串Value化,最常用的方法就是字符串中的每个字符进行相加,得到的值就是Value,如果直接使用Value作为Key,那么Value(ad) = a + d = 97 + 100 = 197 = 98 + 99 = Value(bc)。很明显,立即就产生了碰撞,这当然不是我们想要的一种构造方法了,那继续进行下去,根据高数中的离散性分析,在无系数线性方程中加入系数管控是可以一定程度上加大离散性。所以我们将Value的计算方法进一步修改为:
Value(ad) = k1 * a + k2 * d 。这样得出来的Value就没那么容易产生冲突了,当然也不能完全避免,至于如何最大化的降低冲突的概率,我们就需要研究一下 k1,k2 系数的取值为多少时,可以最大化的降低冲突概率。
系数取值:首先由于字符串无法去评估它的长度,有可能是一个很长的字符串,我们如果人为的去设定 k1,k2 的定值,一旦字符串长了,我们就需要预先设定很多值,这样无疑很麻烦。所以我们可以给出一个固定的系数值k,方程式中的 k1,k1 完全可以替换为 k^0,k^1.....。替换后的方程就是 Value(ad) = k^1 * a + k^0 * d,其中 k ≠ 0,1。设字符串为数组p,元素个数为n,就有公式:
(Ps:这个博客居然不能插入数学公式,我服)
继续取值的问题,到这步我们只需要对 k 进行一个合理的取值就OK。网上看过有人对这个值进行讨论是分为2的幂的偶数、非2的幂的偶数、奇数三个部分讨论。但作为一名学数学的人,总觉得喜欢分为偶数和奇数两种进行讨论,而实验之后发现也完全是可以推导下去的(网上之所以分了2的幂,应该是为了逐步说明,这里就不逐步了)。
假设Value的存储类型为Hash默认的 unsigned int 类型。如果取 k 为偶数,k 总是能分解为 k = 2 * k1(偶数或奇数)。若是字符串的长度大于等于33时,第一位的系数就会变成 k = 2 * k1 ^ 32 = 2 ^ 32 * k1 ^ 32,而熟悉编程的人都知道,如果某个数不断增加,超过类型范围会被直接抛弃,所以无论哪个字符串在乘以 2 ^ 32 之后第一位就会被抛弃,就造成了只要后面32位字符不变化,前面的字符无论怎么改变,都会发生冲突。所以系数去偶数是行不通的。
那再来讨论 k 取奇数,k = 2 * k1 + 1。则 k ^ n = 2 * k1 ^ n + 1。系数后面永远有一个1,所以就可以保证每个字符的改变都会影响到Value值,这样就保证了我们的冲突可能降到最小。上述的方法就是BkdrHash构造算法。
⑵ 一致性Hash构造算法:
一致性Hash的构造经常被用来进行服务器分配,比如一个服务器集群组,可以通过这种Hash分配的方法将新连入的请求分配给某个服务器进行处理。具体的一个原理是:在移除或添加一个Cache时,它能够尽可能小的改变已存在Key映射关系,满足单调性。
对于一致性Hash,我们可以把它想象成一个环形的控件,首为0,尾为2^32 - 1。首先当请求到来时,先将请求映射到Hash空间中,这时候再把服务器的节点映射到Hash空间中,到这里我们先看几个简单的图,来实例一下大致如何映射。
4个请求通过Hash函数映射到哈希表中 3个服务器也进行映射
观察第二张图的箭头,比如 Key1 会被分到 Cache A 处理,Key 4 会被分到 Cache B 处理, Key 2 和 Key 3 会被分给 Cache C处理。如果其中一个服务器挂掉,如Cache B挂掉,就会将 Key 4 的请求交给 Cache C
当然如果说某种情况下导致服务器不均匀,就需要进行负载均衡的原理进行重新搭建虚拟映射,对服务进行统计划分后,将服务器的已存在的映射进行再次虚拟映射,使得每个服务器所接受到的请求基本是平均的
本次的分享大致就是这些,基本都是对Hash有过基本的了解后,通过本次文章,可以大致了解到一些实用的Hash构建方法以及怎么保证冲突最小化的办法。
Hash表从了解到深入(浅谈)的更多相关文章
- $.ajax()方法详解 ajax之async属性 【原创】详细案例解剖——浅谈Redis缓存的常用5种方式(String,Hash,List,set,SetSorted )
$.ajax()方法详解 jquery中的ajax方法参数总是记不住,这里记录一下. 1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为Str ...
- c#Winform程序调用app.config文件配置数据库连接字符串 SQL Server文章目录 浅谈SQL Server中统计对于查询的影响 有关索引的DMV SQL Server中的执行引擎入门 【译】表变量和临时表的比较 对于表列数据类型选择的一点思考 SQL Server复制入门(一)----复制简介 操作系统中的进程与线程
c#Winform程序调用app.config文件配置数据库连接字符串 你新建winform项目的时候,会有一个app.config的配置文件,写在里面的<connectionStrings n ...
- 浅谈字符串Hash
浅谈字符串Hash 本篇随笔讲解Hash(散列表)的一个重要应用:字符串Hash. 关于Hash Hash是一种数据结构,叫做Hash表(哈希表),也叫散列表.关于Hash的实现,其实与离散化颇为类似 ...
- 【ASP.NET MVC系列】浅谈表单和HTML辅助方法
[01]浅谈Google Chrome浏览器(理论篇) [02]浅谈Google Chrome浏览器(操作篇)(上) [03]浅谈Google Chrome浏览器(操作篇)(下) [04]浅谈ASP. ...
- 图书管理(Loj0034)+浅谈哈希表
图书管理 题目描述 图书管理是一件十分繁杂的工作,在一个图书馆中每天都会有许多新书加入.为了更方便的管理图书(以便于帮助想要借书的客人快速查找他们是否有他们所需要的书),我们需要设计一个图书查找系统. ...
- 浅谈Hash在多个字符串匹配类型问题中的应用
在生活中们有时会遇到一些有关字符串匹配的问题. 这时打暴力往往显得很愚蠢,效率低下. 所以就需要一些算法和数据结构来提高效率. Hash Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把 ...
- 浅谈HTML5单页面架构(二)——backbone + requirejs + zepto + underscore
本文转载自:http://www.cnblogs.com/kenkofox/p/4648472.html 上一篇<浅谈HTML5单页面架构(一)--requirejs + angular + a ...
- 转【】浅谈sql中的in与not in,exists与not exists的区别_
浅谈sql中的in与not in,exists与not exists的区别 1.in和exists in是把外表和内表作hash连接,而exists是对外表作loop循环,每次loop循环再对内表 ...
- 浅谈sql中的in与not in,exists与not exists的区别
转 浅谈sql中的in与not in,exists与not exists的区别 12月12日北京OSC源创会 —— 开源技术的年终盛典 » sql exists in 1.in和exists ...
随机推荐
- android 和主线程有关的小问题
1.在android 中 HTTP请求被不允许在主线程中执行,否则会抛出异常.如果请求时间过长,阻塞UI线程是一个非常差的体验之前,刚开始开发遇到这个问题,每次app运行到最后,发送网络请求就自己崩溃 ...
- 洛谷 P1022 计算器的改良
题解:字符串模拟 坑点: 1) 0/-1=-0. 这是因为(来自洛谷讨论区某大犇) double下存储的数字会有精度误差,比如0可能被存成0.000000000...01然而如果你乘上或者除以一个负数 ...
- Gixy Nginx 配置分析工具
项目简介 Gixy 是一款用来分析 Nginx 配置的工具. Gixy 的主要目标是防止安全配置错误,并自动进行缺陷检测. 目前支持的 Python 版本是 2.7 和 3.5+ . 免责声明:Gix ...
- Youtube 视频下载
Youtube 视频下载 由于特殊原因,需要下载 Youtube 的视频. https://www.clipconverter.cc/
- Error unmarshalling file:/opt/test/jboss/server/defalt/conf/bootstrap.xml
启动命令:#/usr/local/jboss/bin/run.sh -b 0.0.0.0 -c defalt 启动的defalt写错了,应该写default.
- 坑爹的AMH
坑爹的 AMH的配置文件在这里: nginx 配置文件1:/usr/local/nginx-generic-1.6/conf/nginx.conf nginx 配置文件2:/home/wwwroot/ ...
- C/S模式与B/
网络程序开发的两种计算模式--C/S模式与B/S模式.两种各有千秋,用于不同场合. C/S适用于专人使用,安全性要求较高的系统: B/S适用于交互性比较频繁的场合,容易被人们所接受,倍受用户和软件开发 ...
- C语言实现简单的单向链表(创建、插入、删除)及等效STL实现代码
实现个算法,懒得手写链表,于是用C++的forward_list,没有next()方法感觉很不好使,比如一个对单向链表的最简单功能要求: input: 1 2 5 3 4 output: 1-> ...
- linux下mysql提示"mysql deamon failed to start"错误的解决方法
操作系统为centos,网站突然连接不上数据库,于是朋友直接重启了一下服务器.进到cli模式下,执行 service myqsld start 发现还是提示"mysql deamon fai ...
- 设计模式—单例模式(java)
一:懒汉式 1: 线程安全的双重锁检查机制 public class Singleton{ private Singleton() {} // 私有构造函数,保证不被外界实例化(不考虑反射) ...