https://www.xuebuyuan.com/2
原址:http://bbs.chinaunix.net/thread-3754752-1-1.html 分享一下对Lua的哈希表结构的研究。
一般哈希表处理冲突有两种方式,拉链法和开放定址法。拉链法就是哈希表的每个元
素都是一个链表,如果有冲突的键就放在链表里面。而开放定址法是如果遇到了冲突,就
再计算一个哈希值,直到没有冲突位置。
拉链法的优点就是实现简单,缺点也是有的:链表会导致低的缓存命中率,并且分配
链表节点本身也会对内存分配器产生压力(主要是大量小块内存分配会导致碎片),而且
有个更严重的问题:不大好估计哈希表的装载因子,因此不大容易判断啥时候需要扩充哈
希表。
而开放定址法避免了这些问题,首先因为所有节点都存在哈希表里面,因此很容易就
能估计装载因子,其次也不会有内存分配的问题,避免了零散碎片或者实现内存池的必要
。不过问题是要找到一个新的地址就需要重新计算哈希,是一个负担,而且如果你在对应
哈希查找不到元素也可能并不是没有元素,而只是之前的冲突导致元素不在它的“主位置
”,这样就需要更多复杂的判断。
解决主位置的问题其实很简单:可以设置一个“墓碑”,删除的时候并不是直接删除
,而是设置一个“墓碑”当发现是墓碑的时候,证明该位置曾经是有元素的,这时就按照
冲突的方式继续查找。总的来说,如果不考虑装载因子,因为要重复搜索哈希表,开放定
址法的查找是会比较慢的。
Lua的哈希表其实我一直都不是特别明白,最近自己实现一个C版本的ninja的时候想
自己实现一个哈希表,就看了看Lua的实现,发现他居然是一个全新的思路!他将拉链法
和开放定址法结合在了一起。具体的做法是这样的:Lua的哈希表主体是开放定址法,即
所有元素都被存放在表中,而不是在链表里。但是,每个元素也的确有一个链表节点。在
开始的时候,Lua维护一个“空闲槽指针”这个指针之后的位置一定都是有元素的。当发
现冲突的时候,会将空闲槽指针前移,以找到一个空闲的槽位,然后元素会被放在这个槽
位里,并在主位置通过链表的方式链接进来!即对插入而言,Lua的哈希表采用的实际上
是不计算哈希的开放定址法(随意找一个槽位),而对于查找而言,因为有链表节点的存
在,是按照拉链法的方式进行查找的。这种方法利用了两者的优点,又规避了缺陷。 我的理解就是利用数组作为单链表 这里面其实还是有一些其他可以优化的地方。比如墓碑还是需要的,这用于在主位置
找不到以后,得得到后续节点的一个索引。但是通过在插入/删除节点时,挪动后续的节
点的方法,就可以省掉墓碑和对应的遍历操作。另外这种哈希表判断装载因子的方式也很
简单——只要空闲槽指针指向表的第一个元素,就意味着表已经满了,这时就需要rehash
了。
 |
230054.html