一、一种HashTable 的实现
当下程序员(尤其是.net 和Java 程序员)写代码,基本上都会拿出HashTable (HashMap )来用。可是你有没有想过实现一个HashTable 呢? 你可能会说那一定很难吧?不要被吓倒,HashTable 没有那么难,下面跟我一步一步的去了解它吧。
基本原理
HashTable 大概可以理解为一种通过关键字符串直接定位(其实也不能叫直接定位,为了表示其高效所以才用了这个词)数据的一种数据结构。
大家都知道HashTable 可以根据键值快速找到(定位)数据,基本上可以说是直接定位,究其根本,HashTable 大多数情况是依靠数组来定位的(Hash 算法另谈),数组定位速度还用说吗?那么我们的键值怎么才能变成数字呢? Hash 算法能解决这个问题。下面来简单了解一下hash 算法。
1 Hash 算法
Hash 算法能把字符串换算成一个数字,不同的字符串可能会被划算成同样的数字;但这并不影响我们使用这个数字在hashtable 中定位数据。
对于Hash 算法,最好是计算速度快,计算结果分布均匀(这个可能不好理解,跳过,慢慢往下看)。Hash 算法有很多种,细研究起来恐怕要下大工夫才行;不过网上可以找很多算法,先拿来用吧。
好了,现在我们知道可以用hash 算法把一串字符换算为数字,可是这个数字的大小可不好说,它绝对不能作为数组下标使用的;如果把这个数字与一个特定的数字(hash_size) 做除法求余,那么这个余数肯定在0 – hash_size 之间,此时,我们就可以用这个余数在一个hash_size 大小的数组中来定位数据了。
下面我们来讨论一下这个hash_size 。
2 HashTable 的大小
每个hashtable 都是有大小(hash_size) 的;我们在使用hashtable 时,特别是Java 和.net 程序员,一般不指定hashtable 的大小,当然如果你不指定大小并不意味着你创建的hashtable 没有大小,因为它们的类库帮你完成了这些工作。
也许你有些不明白,一直使用的hashtable 是没有大小限制的啊?
其实,这个hash_size 并不是限定hashtable 的大小的,而是用来快速定位用的。Hash_size 就是它所使用的数组的长度,这个值跟你要放入hashtable 的数据的数量和Hash 算法共同决定着你的hashtable 定位数据的速度,很关键啊。
在了解HashTable 的具体结构之前,我们需要先知道HashTable 节点的定义。
3 HashTable 节点
HashTable 节点可简单定义如下:
typedef struct s_hnode {
char *key; // 键
void *data; // 值
struct s_hnode *next; // 下一个节点
} HNode, *pHNode;
为什么节点结构要有next 指针呢?在后面回答。
4 HashTable 的结构
HashTable 的结构就是一个长度为hash_size (HashTable 的大小) 的类型为pHNode 的数组,数组的每一项都是指向HNode 的指针,这个节点同时又有一个next 节点。如下图所示:
图1 HashTable 示意图
为什么上图中数组的一项要有next 呢?也就是为什么节点结构要有next 指针呢?这是因为通过对不同的*key 做Hash 运算,然后再整除hash_size 得到的数字可能是相同的,但是又不能把他们存到一起,所以使用一个链表来解决这个问题,这同时说明了hashtable 的容量是没有限制的,也进一步说明hashtable 定位数据的效率低于数组下标定位。
下面来看看hashtable 是怎么生成的吧。
5 生成HashTable
输入要增加到hashtable 的键key_to_insert 和数据data_to_insert
生成Hash 节点pNode
pNode->key = key_to_insert; // 注意:如果是C 语言,不能这么简单的赋值,需要复制一份key_to_insert 给key
pNode->data = data_to_insert;
pNode->next = NULL;
对key_to_insert 做Hash 运算得到hash_value
用hash_value 去整除hash_size 求余,得到item_index
根据item_index 找到链表首指针header = HashTable[item_index];
如果header 为NULL
则把pNode 作为首指针,HashTable[item_index] = pNode;
否则
遍历链表header ,
如果找到pHNode->key == key_to_insert
更新数据pHNode->data = data_to_insert // 记得释放pNode
遍历完,没有找到,到header 链表的结尾,把pNode 加到最后
6 HashTable 的定位过程
其实生成hashtable 的过程完整的包含了HashTable 的定位步骤,下面是简要流程:
输入要定位的key_to_find
对key_to_find 做Hash 运算得到hash_value
用hash_value 去整除hash_size 求余,得到item_index
根据item_index 找到链表首指针header = HashTable[item_index];
遍历链表header ,
如果找到pHNode->key == key_to_find
说明找到数据,返回当前指针
遍历完,没有找到,返回NULL;
7 上面的内容再往深里思考,你会学到更多!
二、HashTable 与云存储
当下云计算、云存储已经不再是虚无缥缈的概念了,我这篇也跟云拉拉关系,呵呵。下面来讨论一下云存储与HashTable 的一些联系。
我这也是胡乱写写,不一定对啊,有兴趣地可以下面讨论。
关于云存储我们先简单理解为数据分布是存储;在实际使用过程中,对于外部开发人员,只需要把键/ 值提交给云存储,然后云通过自己的计算来确定数据存放到那几台服务器中,取数据时同样由云提供的接口自动从服务器中找到数据。下面我要说的是云是怎么通过键确定数据的存储位置的。
回头看看我们的图1 (HashTable 示意图),如果我们把数组的每一项看作是一台存储服务器,那么我们就可以通过与HashTable 相同的计算方法来确定数据应该存放到哪台服务器和应该去哪台服务器去取数据。这样做可以实现数据的快速定位,但是如果我们需要增加数据服务器该怎么办呢?是不是所有数据都需要重新计算,重新分配存储呢?天哪,对于存储巨量数据的云存储来说这是不可思议的事情。怎么办呢?我们下面就学习怎么大面积的解决这个问题的办法-- 哈希环。
之所以说是大面积解决 因为增减服务器数据迁移的问题,是因为哈希环也不能彻底解决这个问题,只不过使数据的迁移量大大减少而已。
哈希环是把HashTable 的数组长度设置为远远大于服务器的数量。
假设我们有4 台存储服务器,我们设定hash_size=1000; 我们可以用下图的形式来存储数据:
在运行过程中我们发现(250 - 499)Server2 的数据量比较大,需要增加一台服务器,我们只需要将Server2 的部分数据迁移到新的server 中即可,而其它服务器中的数据可以保持现状,数据的访问和写入都不会受到影响。
数据迁移过后的哈希环如下图所示:
云计算、云存储岂是我辈三言两语能说得清楚的?就此打住了。