KV存储

KV-存储 之 Hash算法


http://www.cesclub.com/bw/jishuzhongxin/bianchengyuyan/2011/0924/10415.html



生活生计不是局限于人类寻求本身的实际目标所进行的日常步履,而是显示了人类参加到一种宇宙韵律中来,这种韵律以形形的体式格式证实其自身的存在。生活生计中你应当碰到如许的工作:  你要去近邻班级拿本书给某位同窗, 不巧的是他当时不在,你也不知道他的座位, 你会选择问离你比来的同窗,他坐哪. 他会毫不迟疑的答复你: 第几排第几个座位.  这是个很常见的例子, 然则里面却蕴含了极其巨大的Hash算法. 试想 那位同窗能在近似常数时候内帮你指出你要知道的那位同窗的地位是因为所有同窗的地位他已经记在思维中了(假设全部).  他不会去从第一排第一个地位慢慢去搜刮.而是直接定位. ---- 哈希 又叫 散列 

一小我只要他有纯粹的心灵,无愁无恨,他的芳华时代定可是以而耽误。策画机中内存接见速度很快,假如有个64G大小的内存,我须要接见内存中的cache的一段, 想在O(1)时候内进行定位,就须要Hash的帮助。






持续经由过程上方的例子不和得出:

(1).要想进行Hash搜刮 必须先记住每个元素的地位


(2).要想哄骗策画机存储器记住每个元素的地位,必须分派必然的空间来存储这些地位.


(3).要想知道每个元素的地位在哪,必须有个好的Hash算法进行策画每个元素的地位.


这三点可以说浓缩了Hash的精华, 下面首要讲Hash算法.因为是它决意了Hash的短长.




Hash算法(v1~v8为整数)




如图所示, v1~v8 ,8个值 经由过程Hash Alg 得出Hash 桶, 这里Hash Table就像个数组, v1在 索引 0 地位 ...v8在索引 7 地位. 今后 就可以经由过程0~7(key) 对其进行常数时候内的查找了.


这是一个斗劲简单的Hash,也是一个幻想的Hash,为什么说是幻想呢? 请看下面的这张图.



如图所示,假如有10W个值,甚至更大,遵守前面的Hash算法就须要开辟10W个大小的空间(Hash Table).若是元素更多,开辟的空间就更多,显然这不是一个好算法,然则 这又是速度最快的算法,因为查询随便率性元素都在O(1)内.  这是个空间换时候的思惟,然则空间也是有限的,如何调和这是个值得思虑的题目.


当HashTable必然, 哈希的元素个数 <=  HashTable的空间大小 , 则可以在有限的HashTable内 哈希的元素对应不合的索引.

当HashTable必然, 哈希的元素个数  >    HashTable的空间大小,  则会有2个或多个以上的元素被映射到 雷同的索引上. 如下图:



须要哈希的&#20540;v5 和 v9产生了冲突, vv10产生了冲突. 这个冲突就是我们要解决的,冲突的频率决意了一个Hash算法的短长. 冲突越多,Hash算法效力越低. 

其实冲突是不成避免的,比如说,刚才这个例子,我们须要存放10个元素, 而HashTable中只有8个, 幻想景象是须要 10 个HashTable的地位 , 我们可以 对 8 进行取模H(v) = V mod 8 生成的成果会都在0~7之间.

这种Hash算法可以称为 取模Hash ,这种算法很简单,Hash速度很快, 然则 缺点很大,1. 斗劲合适Key为整数景象 2. 若是HashTable的元素数量选的不敷好 则轻易造成分布不均匀,比如 HT(HashTable)的元素数量为 2 的倍数, 须要哈希的元素都为偶数,那么所有 !(X % 2 == 0) 的地位都为空. 为了避免这种景象,凡是包管表的大小为素数.

若是Key为字符串,凡是做法是把字符串中的字符的ASCII码加起来.然后把累加的&#20540;与HT的大小进行取模 .


#define HTSIZE 10007 //为素数
int htV (char* key) {
int i = 0;
int sum = 0;
for (; key[i]!=""""; i++) {
  sum += (int)key[i];
}

return sum % htSize;
}上方就是对字符串进行Hash的一段代码.然则这个Hash函数分布也不是很均匀,假设key最大为8字节长. char的最大&#20540;为127, 是以可以得出Hash函数能在0 ~ 127*8(1016) 之间获得. 冲突率很是高.持续看一段代码.


#define HTSIZE 10007
int htV_better (char* key) {
u16 sum= 0;
while (*key!="""") {
  sum= (sum<<5) + *key++;
}

return sum % htSize;
}
这段代码应用了Horner法例:

假设有n&#43;2个实数a0,a1,…,an,和x的序列,要对多项式Pn(x)= anx ^n&#43;a(n-1)x^(n-1)&#43;…&#43;a1x&#43;a0求&#20540;,直接办法是对每一项分别求&#20540;,并把每一项求的&#20540;累加起来,这种办法十分低效,它须要进行n&#43;(n-1)&#43;…&#43;1=n(n&#43;1)/2次乘法运算和n次加法运算。有没有更高效的算法呢?答案是必然的。经由过程如下变换我们可以获得一种快得多的算法,即Pn(x)= anx ^n&#43;a(n-1)x^(n-1)&#43;…&#43;a1x&#43;a0=((…(((anx &#43;an-1)x&#43;an-2)x&#43; an-3)…)x&#43;a1)x&#43;a0,这种求&#20540;的安排我们称为霍纳法例

下面是该评论辩论下解决Hash冲突的题目了,总结一下有下面几种解决Hash冲突的办法.

链地址算法:

这个是解决冲突最常见的办法,所谓开放是指每个桶都可以进行"开放", 经由过程把桶起冲突元素用链表链接在一路. 如下图. 如许空间斗劲随便




Index = 4 这个地位 ,有3个元素起了冲突,遵守时候插入次序依次分列.

检索过程其实就是Hash&#43;List的过程.先定位到H(v5) 若是则返回.不是则持续读下一个.插入数据时,若是遵守"次序思维"须要一个元素一个元素的遍历下去直到下一个指针为NULL,固然链表进行的是一个线性操纵,然则若是冲突较少,也就是链表较短,效力也是很高的, 其实也可以推敲下链表的特点,我们可以插入在头部,如许插入就为O(1), 效力也就进步上去了. SGI STL中hash table用的就是这种算法。

再经由过程下面的图一步一步熟悉下 开放链地址法

Hash Table初始化为 13 个桶 (从0开端 0~12),Hash算法为对Hash table 取模

Step 1:

向hash Table中插入 1, 2, 3, 4, 5 这5个元素

插入地位分别为 1%13, 2%13, 3%13, 4%13, 5%13



Step 2:

向Hash Table中插入 13, 100

插入的地位为 13%13, 100%13




如图所示,进行模运算后 100地位在 第10个地位,那么如今我插入 9 再看下:




可以看出,在第10个地位生成了一个链表,元素地位是按照时候来入的,就像一个Stack!

如今我们来查找 100 这个元素:

   ------> 形象的描述了 链表的遍历过程





开放定址算法

若是h(k)已经被占用,则按如下序列探查:(h(k)&#43;p(1))%TSize, (h(k)&#43;p(2))%TSize, …,(h(k)&#43;p(i))%TSize,…


此中,h(k)为哈希函数,TSize为哈希表长,p(i)为探查函数。在 h(k)&#43;p(i-1))%TSize的根蒂根基上,若发明冲突,则应用增量 p(i) 进行新的探测,直至无冲突呈现为止。此中,按照探查函数p(i)的不合,开放定址法又分为线性探查法(p(i) = i : 1 , 2 , 3 , …),二次探查法(p(i)=(-1)i-1((i&#43;1)/2)2: 12 , -12 , 22 , -22 …),随机探查法(p(i): 随机数 ),双散列函数法(双散列函数h(key) ,hp (key)若h(key)呈现冲突,则再应用hp (key)求取散列地址。探查序列为:h(k), h(k)&#43; hp(k),…, h(k)&#43; i*hp(k)). --- <百科>


线性探测法:

p(i) = 1, 2, 3 (<TSize-1) 也就是 通项式为 p(i) = i

当发明冲突时 应用p(i)函数进行新的探测,每次移动一个元素,直到无冲突为止.来形象的看下具体过程:

初始化Hash为29个,取模Hash

Step 1: 插入 1




Step 2: 再插入 1



可以看到 1 被放置于 2 的地位,假如2的地位已经有2了 那么1会被放置在哪呢? 我们返回后再来看下成果。

1被放置了2的后面。这个按照(1&#43; p(2)) % 29 可以得出。

二次探查法


通项式为:p(i) = 1,1,4,-4,9,-9 (i*i,-i*i)  [<Tsize/2]


我们插入1,1,1 来看下元素分派:




若是再插入两个1后插入一个5 再插入一个1呢,应当还是再次遵守二次探查法来进行下一步的操纵.




随机探查法


把p(i)从线性探测改成随机数(伪随机) p(i) = Random ,其它类&#20284;。




KV-存储

Hash在存储中应用,也就是KV(key-value)存储,因为Hash本身的限制,KV存储也只能支撑Put, Get, Del几个原语操纵。 然则也已经足够了。

KV存储实现不难,然则有一些器材须要重视,可以让效力提拔一个阶层。

(1). Put操纵时若是你进行及时写,效力很一般,然则你若是应用次序读入内存再批量写入磁盘,速度会提拔很多,也就是所谓的延迟写操纵和批处理惩罚化,这点可以参看Google开源的LevelDB

http://code.google.com/p/leveldb/

,LogTree算法

http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.44.2782


(2).Get操纵建议应用mmap,因为文件映射就&#20284;直接读内存差不久不多(道理是把文件调入过程的地址空间,本博有具体解析)

(3).Del操纵建议直接做标识表记标帜,因为数据量大,不建议直接做真正删除操纵,因为如许会很轻易形成内存碎片. 当然若是你承认本身机制做的好的话可以进行直接删除,然后一按时辰 进行碎片收拾。

你可能感兴趣的:(存储)