5

哈希表(Hash table,也叫散列表),是根据键(Key)而直接访问在内存存储位置的数据结构。 也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。 这个映射函数称做散列函数,存放记录的数组称做散列表。 ————维基百科

在一些合理的假设下,在哈希表中的所有操作的时间复杂度可以简单看作O(1)。它通过计算一个键的哈希值来快速定位键值在哈希表的位置。实现一个好的哈希表的关键是一个好的哈希算法与一个处理哈希冲突的方法
。常见的简单哈希算法有BKDRHash,APHash,DJBHash,JSHash,RSHash.较复杂有MD5和SHA1之流。哈希算法的好坏直接影响哈希表存取的效率。而处理哈希冲突的办法有开放寻址法与拉链法。下面示例使用的是拉链法

BKDRHash哈希算法

上述多个哈希算法中 BKDRHash在效率,平衡,均匀分布方面表现突出,并且它的实现也是最容易理解与记忆的。实现如下:

/** 
 * 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...
image

上图中,左则是一个数组,保存的是每个单向链表的头指针。实际的值就存储在链表中

单向链表中的节点的数据结构与数组定义代码
/** 单向链表的节点的结构体 */
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;
}

打印完整hashtable中的元素

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);

输出结果

#打印⑴ 
xiaoqiong => 99
natalie => 1000
longmon => 100

#打印⑵ 
xiaoqiong => 99
longmon => 100

#打印⑶ 
Key: longmon => val: 100

完整代码 https://github.com/longmon/hashTable_in_c

哈希表是PHP内核的基石。

PHP内核中的哈希表是十分重要的数据结构,PHP的大部分的语言特性都是基于哈希表实现的, 例如:变量的作用域、函数表、类的属性、方法等,Zend引擎内部的很多数据都是保存在哈希表中的。而PHP内核中的哈希表处理冲突使用的也是拉链法。

扩展阅读:


longmon
318 声望6 粉丝