背景介紹
最近使用Python開發項目為主,當使用到字典時感覺非常方便實用。那么好奇心就驅使我要搞清楚字典是怎么實現的。為了真正的搞清楚字典的實現就不得不使用C語言來實現一遍,為此我查了一些資料現在總結一下。
字典簡述
字典也被稱為關聯數組,還稱為哈希數組等。實現的原理一般是有一個鍵值對,通過鍵可以索引到值。很多工具都使用這種方式保存數據,例如redis/memcached/mongo等。所以鍵是唯一的,要是實現字典的快速查詢肯定不能使用字符串遍歷對比的方式實現。那樣實現的字典會非常非常的慢。我們都知道在數組中使用下標索引可以快速的得到對應值,所以我們需要做的就是怎樣把鍵計算出一個唯一值並且這個值要唯一還要在我們的數組索引值范圍呢。舉例子說,當一個鍵為hello
的時候,我的數組最大索引是1024
這個范圍呢!我們的鍵值對可以有1024
對。那么如果按照ascii
值對hello
進行一個簡單的加法計算我們會得到什么呢?下面運行一段c
程序看看。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int sum_ascii(char * key){
unsigned long sum = 0;
unsigned long i;
for(i = 0; i < strlen(key);i++){
sum+= key[i];
}
return sum;
}
int main(int argc, char * argv[]){
int key_id = sum_ascii("hello");
printf("The key id %d\n", key_id);
return 0;
}
看到這個532
你會覺得正好,至少沒有超過1024
這個索引范圍,可以用了。不要着急,我們繼續將hello
改成hellohashtable
看看會得到多少值。
糟糕了!超出索引范圍了,你可能會想到兩種解決方案。
*1,增加數組大小。
*2,寫一篇說明書,說明你的這個鍵不能超過多少個字符。
這兩個方案顯然是無法讓使用者開心的,所以我們需要解決掉。我們可以使用下面的方式獲取絕對不會超過索引范圍的值,猜猜什么辦法。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE_ARRAY 1024
int sum_ascii(char * key){
unsigned long sum = 0;
unsigned long i;
for(i = 0; i < strlen(key);i++){
sum+= key[i];
}
return sum;
}
int residue(int size){
return size % SIZE_ARRAY;
}
int main(int argc, char * argv[]){
int key_id = sum_ascii("hellohashtable");
key_id = residue(key_id);
printf("The key id %d\n", key_id);
return 0;
}
現在好了,我們不會超出索引范圍了。那么問題又來了,我們的計算值很明顯非常容易出現沖突,下面舉一個例子吧。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE_ARRAY 1024
int sum_ascii(char * key){
unsigned long sum = 0;
unsigned long i;
for(i = 0; i < strlen(key);i++){
sum+= key[i];
}
return sum;
}
int residue(int size){
return size % SIZE_ARRAY;
}
int main(int argc, char * argv[]){
char * key1 = "hellolandpack";
char * key2 = "hellohellohellohelloak6";
int key1_id = sum_ascii(key1);
int key2_id = sum_ascii(key2);
key1_id = residue(key1_id);
key2_id = residue(key2_id);
printf("The key[%s] id %d\n", key1,key1_id);
printf("The key[%s] id %d\n", key2,key2_id);
return 0;
}
可以看到,我們的key
顯然是不同的,但是卻生成了同樣的一個索引值。這就會導致數據沖突了。下面換一種方式計算鍵值。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE_ARRAY 1024
int sum_ascii(char * key){
unsigned long sum = 0;
unsigned long i;
for(i = 0; i < strlen(key);i++){
//sum+= key[i];
sum = sum << 8;
sum += key[i];
}
return sum;
}
int residue(int size){
return size % SIZE_ARRAY;
}
int main(int argc, char * argv[]){
char * key1 = "hellolandpack";
char * key2 = "hellohellohellohelloak6";
int key1_id = sum_ascii(key1);
int key2_id = sum_ascii(key2);
key1_id = residue(key1_id);
key2_id = residue(key2_id);
printf("The key[%s] id %d\n", key1,key1_id);
printf("The key[%s] id %d\n", key2,key2_id);
return 0;
}
難道這樣就不會生成重復的鍵值嗎?不可能哈,來看看下面這個測試。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE_ARRAY 1024
int sum_ascii(char * key){
unsigned long sum = 0;
unsigned long i;
for(i = 0; i < strlen(key);i++){
//sum+= key[i];
sum = sum << 8;
sum += key[i];
}
return sum;
}
int residue(int size){
return size % SIZE_ARRAY;
}
int main(int argc, char * argv[]){
//char * key1 = "hellolandpack";
//char * key2 = "hellohellohellohelloak6";
char * key1 = "ddddd";
char * key2 = "dddd";
int key1_id = sum_ascii(key1);
int key2_id = sum_ascii(key2);
key1_id = residue(key1_id);
key2_id = residue(key2_id);
printf("The key[%s] id %d\n", key1,key1_id);
printf("The key[%s] id %d\n", key2,key2_id);
return 0;
}
好了,問題又來了!我們又遇到問題了。這個和之前的算法一樣無法解決唯一鍵值問題。那么干嘛還要引入這個呢?來再看看原來的算法。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE_ARRAY 1024
int sum_ascii(char * key){
unsigned long sum = 0;
unsigned long i;
for(i = 0; i < strlen(key);i++){
//sum+= key[i];
//sum = sum << 8;
sum += key[i];
}
return sum;
}
int residue(int size){
return size % SIZE_ARRAY;
}
int main(int argc, char * argv[]){
//char * key1 = "hellolandpack";
//char * key2 = "hellohellohellohelloak6";
char * key1 = "hello";
char * key2 = "olleh";
int key1_id = sum_ascii(key1);
int key2_id = sum_ascii(key2);
key1_id = residue(key1_id);
key2_id = residue(key2_id);
printf("The key[%s] id %d\n", key1,key1_id);
printf("The key[%s] id %d\n", key2,key2_id);
return 0;
}
好了!可以看到。對於同一組字符的不同順序它居然認為是同一個鍵,顯然是錯誤的。而增加了向右移位處理后我們可以得到如下的結果。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE_ARRAY 1024
int sum_ascii(char * key){
unsigned long sum = 0;
unsigned long i;
for(i = 0; i < strlen(key);i++){
//sum+= key[i];
sum = sum << 8;
sum += key[i];
}
return sum;
}
int residue(int size){
return size % SIZE_ARRAY;
}
int main(int argc, char * argv[]){
//char * key1 = "hellolandpack";
//char * key2 = "hellohellohellohelloak6";
char * key1 = "hello";
char * key2 = "olleh";
int key1_id = sum_ascii(key1);
int key2_id = sum_ascii(key2);
key1_id = residue(key1_id);
key2_id = residue(key2_id);
printf("The key[%s] id %d\n", key1,key1_id);
printf("The key[%s] id %d\n", key2,key2_id);
return 0;
}
總結
現在大概了解了一下設計字典最大的難點就是怎樣去容納各種不同的鍵值,並且產生一個唯一的索引。下一章我將會具體實現一個字典,並且在最后我會將其編譯為一個python
包。
如果你在使用字典或者鍵值數據庫有什么有趣的發現就快快分享分享吧~~