转载请注明:http://blog.csdn.net/hel_wor/article/details/50358539
关于Redis的博客和教程网上都很多,比较全面的是黄建宏的Redis设计与实现
看了书,作者是怎么思考的似乎明白了。但抽象出来的结论又好像和实现的方式不同,所以在看了Redis字典的源代码后,我觉得可以试试用Java或者C#来写一写。看懂了书中表达的,但又像实际没看懂,所以决定自己实现一个。
Redis的字典是通过哈希表来实现的,冲突通过链地址法解决,Redis作为内存数据库也就是通过在内存中构建数据结构来保存数据,就如同搭积木一样,从低向上一步一步就把我们想要的样子搭建出来了,当我们打算保存数据到Redis中时,我们传入键和值,对于键,会先通过hash算法计算键的hash值,在Redis中使用的hash算法是MurmurHash2,我们需要一个映射表,把这个hash值和键,值封装成的对象对应起来,或者会有想法对应的时候是否不需要键,直接把hash值和值对应起来就可以了?保存这个键的目的是在有hash冲突时,这个键是作为查找条件用来遍历链表的,如果能保证计算出来的hash值是分布的,那么我们将所有hash值同时除以某个数结果也是相同分布的,而同时除以某个数可以保证我们得到的hash值与未除前的哈希值有相同的效果并且已经限制在一个范围内。在Redis中是通过&操作来完成的。
在自己写的DLL里,实现了哈希表扩容,步进Rahash和强制Rehash,定义了新增和保存的接口DicSave,移除键值DicRemove,获取值DicGet,字典是否为空IsEmpty,主键是否存在IsExist。
整个程序的结构就如同上图,Dic存放两个哈希表ht[0],ht[1],1表在Rehash时使用,未Rehash时使用0表,存入的键经过MurmurHash2算法获取到hashKey与哈希表中的masksize&操作得到存入哈希节点索引,要保存到字典中的键和值就存放在hashEntry中,即哈希节点,当发现hash冲突时,把新节点放在最前面,这一切通过指针来完成,也就是链地址法。
在自己实现的时候,才发现下面这个结构的实现挺奇妙的。
因为数组,我们保存数据的时候要考虑到数组索引超界的问题,链表的长度是可以扩展的,但这里我们放进数组的不是链表,而是我们自己定义的一个固定大小的类,链表的操作被我们定义在类中的next指针实现了,这样在数组中我们存放了自定义类的地址,在这个地址中有包含了下一个自定义类的地址,这样就把这个结构实现了,并且我们不必将这个结构定义成Array[List(HashEntry)]的格式。虽然我猜测C#中的可能也是这么实现的,不过是不是得看源码才知道了。
要说说这个lastRehashOverFlag标志(上次Rehash执行完毕标志),我定义它的是为了避免在单步Rehash时由于步进过小导致上一次Rehash未完成时又ht[0]又满足Rehash条件,这样会导致第一次Rehash时扩容的ht[1]未替换掉ht[0]又创建了一个哈希表ht,有了这个标志可以使下一次rehash延迟到上一次rehash结束后再进行。
但这个标志后来证实没有用处,因为不会出现上面说的情况,扩容是按2倍的比例执行的,如果rehash之前的表size为n,那么rehash扩容后的表size则为2n,在代码中将ht[0]上的值搬移到ht[1]上是以一个数组索引为单位的,也就是说每步rehash会搬移ht[0]上的哈希节点数组的某个索引上的所有值(包括解决hash冲突产生的链表)到ht[1]上,如果这个索引上的值为null,就跳过去找下一个值不为null的数组索引,因此在2倍的条件下,将ht[0]中的数据完成搬到ht[1]中所需的单步rehash次数会<=n,每次新增的时候rehash一次,那么最终在n次新增过程内,rehash是一定能完成的。只有在表扩展的增量小于原来表的size时才需要这个标志。
因为先执行把数据搬移一次到ht[1],再执行rehash过程中键值加到ht[1],链表的实现使得这两个过程不会产生冲突。如果自动rehash开关为关,那么满足强制rehash条件后就要执行强制rehash,这时候主线程会被阻塞到直到rehash完成,还有一种情况,就是上面说的表扩展的增量小于原来表的size,一段时间后used/size的比例会满足强制rehash条件,但代码中没有考虑这一点。
///
/// 保存成员(添加/修改)
///
/// 主键
/// 值
/// 是否成功
public bool DicSave(string key, string value)
{
//// 散列比例
float radio = ht[0].used / float.Parse(ht[0].size.ToString());
if (autoRehashFlag && radio > 1 && lastRehashOverFlag)
{
//// 如果单步Rehash步进过小导致二次散列比例超界,等待第一次超界处理流程执行完
rehashIndex = 0;
lastRehashOverFlag = false;
}
if (rehashIndex != -1)
{
//// 单步Rehash
if (this.DictRehash(1))
{
Console.WriteLine("单步Rehash完成");
}
}
else
{
//// 否则超界后的下一次添加才会扩容 因为比例不再是维持在1左右所有必须加等号
if (radio >= forceRehashBoundary)
{
//// 强制Rehash
rehashIndex = 0;
forceExpendHashTableFlag = true;
if (this.DictRehash(ht[0].used))
{
Console.WriteLine("强制Rehash完成");
}
}
}
//// 这里逻辑不与上面合并的原因,因为Rehash后rehashIndex已置为-1 但合并的话会导致键值仍会保存到ht[1]保存而非ht[0],而此时ht[1]的哈希节点数组为null,会报异常。
if (rehashIndex == -1)
{
return this.HashEntrySave(ht[0], key, value);
}
else
{
return this.HashEntrySave(ht[1], key, value);
}
}
rehashIndex是用来记录上一次rehash执行到的索引位置,但rehashIndex == sizemask时,就表示Rehash已完成,可以用ht[1]表替换ht[0],并将ht[1]表重置,rehashIndex置为-1, 等待下次Rehash。
rehash代码如下:
///
/// 重新散列
///
/// 此参数控制Rehash步长
/// 是否成功
private bool DictRehash(int n)
{
try
{
//// rehashIndex == 0作为扩容标志
if (rehashIndex == 0)
{
//// 强制扩容
if (forceExpendHashTableFlag == true)
{
ht[1] = HashTable.ForceExpendHashTable(ht[0].used);
}
else
{
ht[1] = HashTable.ExpendHashTable(ht[0].size);
}
}
while (n-- != 0)
{
//// 只需要处理哈希节点不为空的数组索引
while (ht[0].hashEntryArr[rehashIndex] == null)
{
rehashIndex++;
}
HashEntry entry = ht[0].hashEntryArr[rehashIndex];
HashEntry nextEntry = null;
//// 对每个节点都做处理
while (entry != null)
{
//// 每个节点取出来单独处理,保存下一个节点的地址
nextEntry = entry.next;
entry.next = null;
int hashKey = HashTable.DictGenHashFunction(entry.key);
if (ht[1].hashEntryArr[hashKey & ht[1].sizemask] == null)
{
ht[1].hashEntryArr[hashKey & ht[1].sizemask] = entry;
}
else
{
HashEntry temp = ht[1].hashEntryArr[hashKey & ht[1].sizemask];
entry.next = temp;
ht[1].hashEntryArr[hashKey & ht[1].sizemask] = entry;
}
//// 更新已占用的数值
ht[1].used++;
ht[0].used--;
entry = nextEntry;
}
//// 处理完一条索引下滑1
rehashIndex++;
//// 循环结束条件
if (ht[0].used == 0)
{
//// 更新标志 重置哈希表
rehashIndex = -1;
//// 上次Rehash执行结束
lastRehashOverFlag = true;
ht[0] = ht[1];
ht[1] = null;
return true;
}
}
return true;
}
catch (Exception ex)
{
throw ex;
}
}
异常直接抛出了,其作为组件的作用已经完成。异常就丢给调用方去处理。
增,删,改,查,在写DicRemove处理指针时遇到一个问题。
public bool DicRemove(string key)
{
int count = 0;
bool removeFlag = false;
int hashKey = HashTable.DictGenHashFunction(key);
HashEntry temp = ht[0].hashEntryArr[hashKey & ht[0].sizemask];
HashEntry lastEntry = null;
while (temp != null)
{
if (temp.key == key)
{
//// 如果刚好排在第一个位置
if (count == 0)
{
ht[0].hashEntryArr[hashKey & ht[0].sizemask] = temp.next;
//// 处理引用关系,使其能被GC回收
temp.next = null;
removeFlag = true;
break;
}
if (temp.next == null)
当时写错了代码。
ht[0].hashEntryArr[hashKey & ht[0].sizemask] = temp.next;
写成了
ht[0].hashEntryArr[hashKey & ht[0].sizemask].next = temp.next;
我本想实现的是这个功能:
但最后出来的结果是该删掉的第一个哈希节点还在,但这个hash节点next指针原本存在的节点现在却成了null。
原因抽象出来是这样:
虽然我现在都没弄清楚为什么当时会想到数组会有next指针,next指针才会指向第一个hash节点,明明是数组封装的hash节点才有next指针,而后这个节点就是第一个hash节点。现在不晓得当时咋想的。
下面的都是源代码,只是我看了redis字典的源码认为自己大致理解了其原理自己写的一个,虽然代码的功能我的测试过了实现了,但如果要阅读,我还是建议去读redis的源码。
Dict类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Dict
{
public class Dict
{
///
/// 哈希表
///
public static HashTable[] ht = new HashTable[2];
///
/// 强制rehash边界
///
private static int forceRehashBoundary = 2;
///
/// 重新散列记录 默认值表示未进行rehash
///
private static int rehashIndex;
///
/// 上次Rehash执行完毕标志 首次执行默认为true
///
private static bool lastRehashOverFlag = true;
///
/// 判断是否存在时保存的value值,避免获取时二次查询
///
private static string getValue;
///
/// 自动rehash开关 默认为开
///
private bool autoRehashFlag = true;
///
/// 强制扩展hash表标志
///
private bool forceExpendHashTableFlag = false;
///
/// 哈希表
///
public HashTable[] Ht
{
get { return ht; }
private set { ht = value; }
}
///
/// 自动rehash开关
///
public bool AutoRehashFlag
{
get { return this.autoRehashFlag; }
set { this.autoRehashFlag = value; }
}
///
/// 创建字典
///
/// 字典
public static Dict NewInstance()
{
Dict dic = new Dict();
rehashIndex = -1;
dic.autoRehashFlag = false;
dic.Ht[0] = HashTable.NewInstance();
return dic;
}
///
/// 字典是否为空
///
/// 结果
public bool IsEmpty()
{
return ht[0].used == 0;
}
///
/// 保存成员(添加/修改)
///
/// 主键
/// 值
/// 是否成功
public bool DicSave(string key, string value)
{
//// 散列比例
float radio = ht[0].used / float.Parse(ht[0].size.ToString());
if (autoRehashFlag && radio > 1 && lastRehashOverFlag)
{
//// 如果单步Rehash步进过小导致二次散列比例超界,等待第一次超界处理流程执行完
rehashIndex = 0;
lastRehashOverFlag = false;
}
if (rehashIndex != -1)
{
//// 单步Rehash
if (this.DictRehash(1))
{
Console.WriteLine("单步Rehash完成");
}
}
else
{
//// 否则超界后的下一次添加才会扩容 因为比例不再是维持在1左右所有必须加等号
if (radio >= forceRehashBoundary)
{
//// 强制Rehash
rehashIndex = 0;
forceExpendHashTableFlag = true;
if (this.DictRehash(ht[0].used))
{
Console.WriteLine("强制Rehash完成");
}
}
}
//// 这里逻辑不与上面合并的原因,因为Rehash后rehashIndex已置为1 如果合并的话会导致走是ht[1]保存而非ht[0]
if (rehashIndex == -1)
{
return this.HashEntrySave(ht[0], key, value);
}
else
{
return this.HashEntrySave(ht[1], key, value);
}
}
///
/// 主键是否存在
///
/// treu/false
public bool IsExist(string key)
{
bool existFlag = false;
int hashKey = HashTable.DictGenHashFunction(key);
HashEntry temp = ht[0].hashEntryArr[hashKey & ht[0].sizemask];
while (temp != null)
{
if (temp.key == key)
{
existFlag = true;
getValue = temp.value;
break;
}
//// 可能有哈希冲突 对每个成员做判断
temp = temp.next;
}
if (existFlag == false)
{
//// 如果正在执行Rehash 再去ht[1]中查找
if (rehashIndex != -1)
{
temp = ht[1].hashEntryArr[hashKey & ht[1].sizemask];
while (temp != null)
{
if (temp.key == key)
{
existFlag = true;
getValue = temp.value;
break;
}
temp = temp.next;
}
}
}
return existFlag;
}
///
/// 移除键值
///
/// 主键
/// true/false
public bool DicRemove(string key)
{
int count = 0;
bool removeFlag = false;
int hashKey = HashTable.DictGenHashFunction(key);
HashEntry temp = ht[0].hashEntryArr[hashKey & ht[0].sizemask];
HashEntry lastEntry = null;
while (temp != null)
{
if (temp.key == key)
{
//// 如果是第一个
if (count == 0)
{
ht[0].hashEntryArr[hashKey & ht[0].sizemask] = temp.next;
//// 处理引用关系,使其能被GC回收
temp.next = null;
removeFlag = true;
break;
}
if (temp.next == null)
{
//// 如果是唯一的一个
if (count == 0)
{
ht[0].hashEntryArr[hashKey & ht[0].sizemask] = null;
removeFlag = true;
break;
}
//// 如果是最后一个
if (count > 0)
{
lastEntry.next = null;
removeFlag = true;
break;
}
}
//// 如果在中间
lastEntry.next = temp.next;
temp.next = null;
removeFlag = true;
break;
}
//// 保存上一个节点
lastEntry = temp;
//// 可能有哈希冲突 对每个成员做判断
temp = temp.next;
count++;
}
if (removeFlag == false)
{
//// 如果正在执行Rehash 再去ht[1]中查找
if (rehashIndex != -1)
{
count = 0;
temp = ht[1].hashEntryArr[hashKey & ht[1].sizemask];
while (temp != null)
{
if (temp.key == key)
{
//// 如果是第一个
if (count == 0)
{
ht[1].hashEntryArr[hashKey & ht[1].sizemask] = temp.next;
temp.next = null;
removeFlag = true;
break;
}
if (temp.next == null)
{
//// 如果是唯一的一个
if (count == 0)
{
ht[1].hashEntryArr[hashKey & ht[1].sizemask] = null;
removeFlag = true;
break;
}
//// 如果是最后一个
if (count > 0)
{
lastEntry.next = null;
removeFlag = true;
break;
}
}
//// 如果在中间
lastEntry.next = temp.next;
temp.next = null;
removeFlag = true;
break;
}
lastEntry = temp;
temp = temp.next;
count++;
}
}
if (removeFlag)
{
//// 更新已存入节点数
ht[1].used--;
}
}
else
{
ht[0].used--;
}
return removeFlag;
}
///
/// 获取值
///
/// 主键
/// value值
public string DicGet(string key)
{
if (string.IsNullOrEmpty(key))
{
return null;
}
if (!this.IsExist(key))
{
return "DicGet ERROR : 字典中不存在此主键";
}
return getValue;
}
///
/// 保存哈希节点(新增/修改)
///
/// 哈希表
/// 主键
/// 值
/// 添加结果
private bool HashEntrySave(HashTable ht, string key, string value)
{
try
{
//// 这种情况会出现在误用ht[1]上
if (ht == null)
{
return false;
}
bool isExist = false;
int hash = HashTable.DictGenHashFunction(key);
if (ht.hashEntryArr[hash & ht.sizemask] == null)
{
HashEntry node = HashEntry.NewInstance(key, value, null);
ht.hashEntryArr[hash & ht.sizemask] = node;
ht.used += 1;
return true;
}
else
{
HashEntry temp = ht.hashEntryArr[hash & ht.sizemask];
while (temp != null)
{
if (temp.key == key)
{
isExist = true;
temp.value = value;
}
temp = temp.next;
}
//// 存在就修改 不存在则添加
if (isExist)
{
return true;
}
else
{
HashEntry node = HashEntry.NewInstance(key, value, null);
HashEntry elder = ht.hashEntryArr[hash & ht.sizemask];
ht.hashEntryArr[hash & ht.sizemask] = node;
node.next = elder;
ht.used += 1;
return true;
}
}
}
catch (Exception ex)
{
throw ex;
}
}
///
/// 重新散列
///
/// 此参数控制Rehash步长
/// 是否成功
private bool DictRehash(int n)
{
try
{
//// rehashIndex == 0作为扩容标志
if (rehashIndex == 0)
{
//// 强制扩容
if (forceExpendHashTableFlag == true)
{
ht[1] = HashTable.ForceExpendHashTable(ht[0].used);
}
else
{
ht[1] = HashTable.ExpendHashTable(ht[0].size);
}
}
while (n-- != 0)
{
//// 只需要处理哈希节点不为空的数组索引
while (ht[0].hashEntryArr[rehashIndex] == null)
{
rehashIndex++;
}
HashEntry entry = ht[0].hashEntryArr[rehashIndex];
HashEntry nextEntry = null;
//// 对每个节点都做处理
while (entry != null)
{
//// 每个节点取出来单独处理,保存下一个节点的地址
nextEntry = entry.next;
entry.next = null;
int hashKey = HashTable.DictGenHashFunction(entry.key);
if (ht[1].hashEntryArr[hashKey & ht[1].sizemask] == null)
{
ht[1].hashEntryArr[hashKey & ht[1].sizemask] = entry;
}
else
{
HashEntry temp = ht[1].hashEntryArr[hashKey & ht[1].sizemask];
entry.next = temp;
ht[1].hashEntryArr[hashKey & ht[1].sizemask] = entry;
}
//// 更新已占用的数值
ht[1].used++;
ht[0].used--;
entry = nextEntry;
}
//// 处理完一条索引下滑1
rehashIndex++;
//// 循环结束条件
if (ht[0].used == 0)
{
//// 更新标志 重置哈希表
rehashIndex = -1;
//// 上次Rehash执行结束
lastRehashOverFlag = true;
ht[0] = ht[1];
ht[1] = null;
return true;
}
}
return true;
}
catch (Exception ex)
{
throw ex;
}
}
}
}
hashTable类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace Dict
{
///
/// 哈希表
///
public class HashTable
{
///
/// 哈希变长度
///
public int size;
///
/// 哈希掩码
///
public int sizemask;
///
/// 已添加的节点数
///
public int used;
///
/// 哈希节点数组
///
public HashEntry[] hashEntryArr;
///
/// 创建hash表
///
///
public static HashTable NewInstance()
{
HashTable instance = new HashTable();
instance.size = 4;
instance.sizemask = instance.size - 1;
instance.used = 0;
instance.hashEntryArr = new HashEntry[4];
return instance;
}
///
/// 扩展hash表
///
/// 扩展前的hash表大小
/// hash表
public static HashTable ExpendHashTable(int size)
{
int i = 0;
while (Math.Pow(2, i) < size * 2)
{
i++;
}
HashTable instance = new HashTable();
instance.size = int.Parse(Math.Pow(2, i).ToString());
instance.sizemask = instance.size - 1;
instance.used = 0;
instance.hashEntryArr = new HashEntry[instance.size];
return instance;
}
///
/// 强制扩展hash表
///
/// 扩展前已装入的成员数
/// hash表
public static HashTable ForceExpendHashTable(int used)
{
int i = 0;
while (Math.Pow(2, i) < used)
{
i++;
}
HashTable instance = new HashTable();
instance.size = int.Parse(Math.Pow(2, i).ToString());
instance.sizemask = instance.size - 1;
instance.used = 0;
instance.hashEntryArr = new HashEntry[instance.size];
return instance;
}
///
/// Thomas Wang's 32
///
/// 待计算HASH主键
/// 哈希值
public int DictIntHashFunction(int key)
{
key += ~(key << 15);
key ^= (key >> 10);
key += (key << 3);
key ^= (key >> 6);
key += ~(key << 11);
key ^= (key >> 16);
return key;
}
///
/// 获取字符串的hash值
///
/// 字符串主键
/// 散列因子
/// hash值
public static int DictGenHashFunction(string key)
{
int seed = 5381;
int m = 0x5bd1e995;
int r = 24;
byte[] data = Encoding.ASCII.GetBytes(key);
int length = data.Length;
if (length == 0)
{
return 0;
}
int h = seed ^ (int)length;
int currentIndex = 0;
int[] hackArray = new int[length];
int i = 0;
foreach (var item in data)
{
hackArray[i] = int.Parse(item.ToString());
i++;
}
while (length >= 4)
{
int k = hackArray[currentIndex++];
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
length -= 4;
}
currentIndex *= 4; // fix the length
switch (length)
{
case 3:
h ^= (int)(data[currentIndex++] | data[currentIndex++] << 8);
h ^= (int)data[currentIndex] << 16;
h *= m;
break;
case 2:
h ^= (int)(data[currentIndex++] | data[currentIndex] << 8);
h *= m;
break;
case 1:
h ^= data[currentIndex];
h *= m;
break;
default:
break;
}
// Do a few final mixes of the hash to ensure the last few
// bytes are well-incorporated.
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return h;
}
}
}
hashEntry类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Dict
{
///
/// 哈希节点
///
public class HashEntry
{
///
/// 主键
///
public string key;
///
/// 值
///
public string value;
///
/// 下一个哈希节点
///
public HashEntry next;
///
/// 返回特定实例
///
/// 主键
/// 值
/// 下一节点
/// 特定实例
public static HashEntry NewInstance(string key, string value, HashEntry next)
{
HashEntry node = new HashEntry();
node.key = key;
node.value = value;
node.next = next;
return node;
}
///
/// 返回默认实例
///
/// 默认实例
public static HashEntry NewInstance()
{
HashEntry node = new HashEntry();
node.key = string.Empty;
node.value = string.Empty;
node.next = null;
return node;
}
}
}