哈希表(Hash table,也叫散列表),是根据键(Key)而直接访问在内存存储位置的数据结构。 也就是说,它经过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。 这个映射函数称作散列函数,存放记录的数组称作散列表。 ————维基百科php
在一些合理的假设下,在哈希表中的全部操做的时间复杂度能够简单看做O(1)。它经过计算一个键的哈希值来快速定位键值在哈希表的位置。实现一个好的哈希表的关键是一个好的哈希算法与一个处理哈希冲突的方法
。常见的简单哈希算法有BKDRHash,APHash,DJBHash,JSHash,RSHash.较复杂有MD5和SHA1之流。哈希算法的好坏直接影响哈希表存取的效率。而处理哈希冲突的办法有开放寻址法与拉链法。下面示例使用的是拉链法html
上述多个哈希算法中 BKDRHash在效率,平衡,均匀分布方面表现突出,而且它的实现也是最容易理解与记忆的。实现以下:git
/** * BKDR hash function in clang * @param key char* 字符串 * @return int hashed value, we will get a int value */ int bkdrHash( char *key ) { uint seed = 131;//i can be 13, 131, 1313,131313 etc uint hash = 0; while( *key != '\n' && *key != 0 ) { hash = hash * seed + ( *key++ ); } return ( hash & 0x7FFFFFFF ); }
示意图:(图片来源:http://blog.csdn.net/v_JULY_v...)github
上图中,左则是一个数组,保存的是每一个单向链表的头指针。实际的值就存储在链表中算法
/** 单向链表的节点的结构体 */ typedef struct _htItem{ struct _htItem *next; char *key_string; uint fid; } htItem; /** 取得Key在数组中的映射位置 */ uint htIndex( char *key, htItem **ht ){ uint hashedKey = bkdrHash( key ); uint length = (ht[0]->fid-1); return (uint)hashedKey % length+1; } /** 定义数组 */ htItem *item[21]; //为方便后台实现,在定义在数组的时候,咱们给多一个长度,第一个数据不存储数据,只存放数组长度,剩余的元素空间
/** 有必要对数组元素进行一次0值 初始化 */ void htInit( htItem **ht, uint length ){ int i; for( i=0; i<length; i++ ){ ht[i] = (htItem*)malloc(sizeof(htItem)); memset( ht[i], 0, sizeof(htItem) ); } ht[0]->fid = length; }
首先经过哈希函数找到Key对应在数组中的位置,再遍历链表,若是在链表中查到相同的key,就直接把该节点的数据更新,不然malloC一个节点,并把节点放到链表末尾。数组
/** set hashTable element */ uint htSet( char *key, uint fid, htItem **ht ){ uint i = htIndex( key, ht ); htItem *item = ht[i]; while( item->next ) { if( strcmp(key, item->next->key_string) == 0 ){ item->next->fid = fid; return 0; }else{ item = item->next; } } item->next = (htItem*)malloc(sizeof(htItem)); item->next->fid = fid; item->next->key_string = key; return 0; }
首先经过哈希函数定位KEY在数组中的位置,再依次对比KEY值,若是命中就返回相应的值,不然返回空值数据结构
/** get hashTable elements */ htItem* htGet( char *key, htItem **ht ){ uint i = htIndex( key, ht); htItem *item = ht[i]->next; htItem *tmp = (htItem*)malloc(sizeof(htItem)); memset(tmp, 0, sizeof(htItem)); while( item ) { if( strcmp(key, item->key_string) == 0 ){ printf("hiting %s\n", key); tmp->fid = item->fid; tmp->key_string = item->key_string; return tmp; } item = item->next; } return; }
/** delete one element of hashtable */ int htDel(char *key, htItem **ht){ uint i = htIndex(key, ht); htItem *item = ht[i]; while(item->next){ if(strcmp(key, item->next->key_string) == 0 ){ htItem *tmp = item->next; item->next = tmp->next; free(tmp); return 0; } item = item->next; } return -1; }
void print_hashTable( htItem **ht ) { uint length = ht[0]->fid; uint i; htItem *item; for( i = 1; i < length; i++ ) { item = ht[i]->next; while(item) { printf("%s => %d\n",item->key_string, item->fid); item = item->next; } } }
简单几个方法就实现了哈希表的增删查改。函数
/** 定义与初始化 */ htItem *item[101]; htInit(item, 101); /** 添加元素 */ htSet("longmon",100,item); htSet("natalie", 1000,item); htSet("xiaoqiong",99, item); /** 打印⑴ */ print_hashTable(item); /** 删除一个元素 */ htDel("natalie", item); /** 打印⑵ */ print_hashTable(item); /** 获取key为longmon的值 */ htItem *tmpitem = htGet("longmon", item); /** 打印⑶ */ printf("key: %s => val:%d\n", tmpitem->key_string, tmpitem->fid);
输出结果ui
#打印⑴ xiaoqiong => 99 natalie => 1000 longmon => 100 #打印⑵ xiaoqiong => 99 longmon => 100 #打印⑶ Key: longmon => val: 100
PHP内核中的哈希表是十分重要的数据结构,PHP的大部分的语言特性都是基于哈希表实现的, 例如:变量的做用域、函数表、类的属性、方法等,Zend引擎内部的不少数据都是保存在哈希表中的。而PHP内核中的哈希表处理冲突使用的也是拉链法。
扩展阅读: