简单说明
hashtable是根据key查询value的一种数据结构,使用数组结构来存储所有的元素,使用一种方式将key映射到数组的不同下标,查询时key就可以直接映射到value,时间复杂度为O(1),因此,hashtable结构经常用于查询的目的。
要实现hashtable,主要有以下2点
1.寻找一种隐射关系,将key映射到数组的某个下标,本文使用简单的取余方式来得
到key在元素中的下标,如果为了hashtable映射更具有一致性
(尽可能的多隐射到不同的下标,少产生冲突),请自行阅读更多的资料
2.不同的key可能会映射到数组的同一个下标,那么如何处理这种冲突,
本文使用的链表来处理冲突。
本文不对理论做过多说明,只用代码说明是如何实现的,如果觉得实现有问题,欢迎拍砖。
实现
头文件
#ifndef LINKED_HASHTABLE_H
#define LINKED_HASHTABLE_H
#include <stdint.h>
typedef void (*free_hashtable_func)(void *); ///< 释放函数指针声明
/**************************************************************************
* @file linked_hashtable.h
* @brief 使用链表方式处理冲突的哈希表
* @author liqiang
* @date 2021.6.8
* @modify 2022.5.25
* @version v1.0
**************************************************************************/
#define CKEYHASHTABLESIZE 8317 /**< hash桶元素的最大值*/
#define HASH_TABLE_OUT_MEMORY -1
#define HASH_TABLE_NO_CUR_ELEMENT -2 //不存在当前元素
#define HASH_TABLE_NO_CUR_KEY -3 //不存在当前元素的key
#define HASH_TABLE_NO_EXIST -4
#define HASH_TABLE_NO_KEY_ELEMENT -5 //table中不存在对应key的元素
#define HASH_TABLE_KEYALREADYEXISTS -6 //table中不能插入key存在的值
struct key_hash_table;
struct key_hash_element;
/**************************************************************************
* @brief hash_table_new 创建hashtable对象
* @param table 要创建的table
* @param cover_old_value[in] 如果为0,则添加时,新值不能覆盖旧值,如果为其他,则可以
* 覆盖
* @return 成功创建返回0,否则返回 HASH_TABLE_OUT_MEMORY
**************************************************************************/
int hash_table_new(struct key_hash_table **table, int cover_old_value);
/**************************************************************************
* @brief hash_table_free 清空表的元素和响应的内存空间
* @param table
**************************************************************************/
void hash_table_free(struct key_hash_table *table);
/**************************************************************************
* @brief hash_table_custom_free 释放表及其元素的相关内存,元素的释放工作
* 由free_func指定
* @param table 要释放的table
* @param free_func 释放元素的操作函数指针
**************************************************************************/
void hash_table_custom_free(struct key_hash_table *table,
free_hashtable_func free_func);
/**************************************************************************
* @brief hash_table_goto_first_element 设置hashtable 指向第一个元素
* @param table
**************************************************************************/
void hash_table_goto_first_element(struct key_hash_table *table);
/**************************************************************************
* @brief hash_table_goto_last_element 设置hashtable 指向最后一个元素
* @param table
**************************************************************************/
void hash_table_goto_last_element(struct key_hash_table *table);
/**************************************************************************
* @brief hash_table_has_cur_element 判断hashtable 当前元素是否存在
* @param table
* @return 存在返回1,否则返回0
**************************************************************************/
int hash_table_has_cur_element(struct key_hash_table *table);
/**************************************************************************
* @brief hash_table_del_cur_element 删除hashtable 当前元素
* @param table
* @return 成功返回0,否则返回小于0的错误码(HASH_TABLE_NO_CUR_ELEMENT)
**************************************************************************/
int hash_table_del_cur_element(struct key_hash_table *table);
/**************************************************************************
* @brief hash_table_get_cur_element 返回hashtable 当前元素的value
* @param table
* @return 如果table不为空,返回hashtable 当前元素的value,否则返回NULL
**************************************************************************/
void *hash_table_get_cur_element(struct key_hash_table *table);
/**************************************************************************
* @brief hash_table_get_cur_key 返回hashtable 当前元素的key
* @param table
* @return 如果table存在,返回当前元素的key,否则返回 HASH_TABLE_NO_CUR_KEY
**************************************************************************/
uint32_t hash_table_get_cur_key(struct key_hash_table *table);
/**************************************************************************
* @brief hash_table_goto_key_element hashtable当前指针指向hashtable
* 指定key对应的的元素
* @param table
* @param key 指定的key
* @return 找到返回0,没找到返回错误码
**************************************************************************/
int hash_table_goto_key_element(struct key_hash_table *table,const uint32_t key);
/**************************************************************************
* @brief hash_table_has_key_element 根据key判断对应的hash桶的下标是否存在对应的元素
* @param table[in]
* @param key[in] 指定的key
* @return 存在key的元素返回TRUE,不存在对应key返回0
**************************************************************************/
int hash_table_has_key_element(struct key_hash_table *table, const uint32_t key);
/**************************************************************************
* @brief hash_table_add_element 往hashtable中添加元素
* @param table[in]
* @param key[in] 添加的key
* @param value[in] 添加的value值
* @param b_need_free_value[in] 是否需要删除元素值的内存
* @return 添加成功返回0,否则返回错误码
**************************************************************************/
int hash_table_add_element(struct key_hash_table* table,const uint32_t key,
void *value,int b_need_free_value);
/**************************************************************************
* @brief hash_table_goto_next_element 获取当前元素的下一个元素
* @param table[in]
**************************************************************************/
void hash_table_goto_next_element(struct key_hash_table* table);
/**************************************************************************
* @brief hash_table_goto_prev_element 获取当前元素的前一个元素
* @param table[in]
**************************************************************************/
void hash_table_goto_prev_element(struct key_hash_table* table);
/**************************************************************************
* @brief hash_table_del_key_element 从hashtable中删除指定key的元素
* @param table[in]
* @param key[in] 指定的key
* @return 成功返回0,否则返回错误码
**************************************************************************/
int hash_table_del_key_element(struct key_hash_table *table,const uint32_t key);
#endif //
C文件
#include <stdio.h>
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "linked_hashtable.h"
/****************************************************************************
*brief RtpKeyHashElement为 hashtable的元素结构
**************************************************************************/
typedef struct key_hash_element
{
int hashindex; /**< 元素占用的下标位置 */
int b_need_free_value; /**< 是否需要删除元素value的内存 */
uint32_t key; /**< 元素的key(使用无符号32位作为元素的key)*/
void* value; /**< 元素的value */
struct key_hash_element *hashprev,*hashnext; /**< hash桶的前后元素指针(横向列表,不同的key得到相同的hash值,链表连接这些元素) */
struct key_hash_element *listprev,*listnext; /**< 哈希桶的纵向数组的列表(寻找不同下标的元素) */
}key_hash_element_t;
/**************************************************************************
*brief RtpKeyHashTabale为 hashtable 结构
**************************************************************************/
typedef struct key_hash_table
{
key_hash_element_t *element_table[CKEYHASHTABLESIZE]; /**< 指针数组:有CKEYHASHTABLESIZE个指针元素的数组,每个指针指向一个key_hash_element_t特有的元素)*/
key_hash_element_t *firsthashelem,*lasthashelem; /**< table第一个元素和最后一个元素的指针*/
key_hash_element_t *curhashelem; /**< 当前元素的指针*/
int cover_old_value;/**< 添加时,该变量用于记录是否用新值覆盖旧值,1-覆盖,0-不覆盖*/
}key_hash_table;
/**************************************************************
* @brief getRtpHashTableIndex 根据key获取下标index(hash函数)
* @param key 元素key
* @return 返回元素key的index
**************************************************************/
static inline int get_hash_table_index(const uint32_t key)
{
return key % CKEYHASHTABLESIZE;
}
int hash_table_new(key_hash_table **table, int cover_old_value)
{
key_hash_table *temp = *table;
if(temp != 0) memset(temp,0,0);
else
{
temp = (key_hash_table *)calloc(1, sizeof(key_hash_table));
}
if(temp == NULL) return HASH_TABLE_OUT_MEMORY;
temp->cover_old_value = cover_old_value;
*table = temp;
return 0;
}
void hash_table_free(key_hash_table *table)
{
if(table == NULL) return;
key_hash_element_t *list_tmp = NULL,*list_tmp_next = NULL;//用于存放hashtable桶的元素:list_tmp从第一个开始,list_tmp用于保存哈希桶下一个元素(用于纵向列表)
/**
* 释放hashtable所有元素的内存空间
*/
list_tmp = table->firsthashelem;
while(list_tmp != 0)//释放元素内存空间
{
key_hash_element_t *hash_tmp,*hash_next_tmp; //用于保存横向hashtable的元素,可能多个不同的key,hash值是一样的,这时候就需要在桶中的同一个元素使用链表标识
hash_tmp = list_tmp->hashnext;//
while(hash_tmp != 0)
{
hash_next_tmp = hash_tmp->hashnext;
if(hash_tmp->b_need_free_value) //如果需要删除元素内容空间
{
free(hash_tmp->value);
hash_tmp->value = NULL;
}
free(hash_tmp);
hash_tmp = hash_next_tmp;
}
list_tmp_next = list_tmp->listnext;//获取hashtbale的下一个元素
if(list_tmp->b_need_free_value)
{
free(list_tmp->value);
list_tmp->value = NULL;
}
free(list_tmp);
list_tmp = list_tmp_next;
}
table->firsthashelem = 0;
table->lasthashelem = 0;
table->curhashelem = 0;
for (int i = 0 ; i < CKEYHASHTABLESIZE ; i++)
table->element_table[i] = NULL;
free(table);//释放table自身
}
int hash_table_del_cur_element(key_hash_table *table)
{
if(table == NULL) return 0;
if (table->curhashelem)
{
key_hash_element_t *list_tmp,*list_tmp_next;//用于存放hashtable桶的元素:list_tmp从第一个开始,list_tmp_next用于保存下一个元素(用于纵向列表)
int index;
/**
*在hash桶中重新设置连接
*/
index = table->curhashelem->hashindex;//获取下标位置
list_tmp = table->curhashelem->hashprev;//横向的前一个元素
list_tmp_next = table->curhashelem->hashnext;
if (list_tmp == 0)
{
table->element_table[index] = list_tmp_next;//设置为当前元素的下一个元素
if (list_tmp_next != 0)//如果存在,则设置前一个元素为空,如果不存在下一个元素,相当于table[index] = 0
list_tmp_next->hashprev = 0;
}
else //
{
list_tmp->hashnext = list_tmp_next;
if (list_tmp_next != 0)
list_tmp_next->hashprev = list_tmp;
}
/**
*在hash桶中重新设置连接
*/
list_tmp = table->curhashelem->listprev;
list_tmp_next = table->curhashelem->listnext;
if (list_tmp == 0) //要删除的元素为hash桶中为第一个元素
{
table->firsthashelem = list_tmp_next; //将要删除元素的下一个元素设置为第一个元素
if (list_tmp_next != 0)//如果下一个元素存在,设置下一个元素的前一个元素为空
list_tmp_next->listprev = 0;
else //要删除的元素同时也是最后一个元素,说明只有一个元素,这时候相当于头和尾都设置为空
table->lasthashelem = 0;
}
else
{
list_tmp->listnext = list_tmp_next;
if (list_tmp_next != 0)
list_tmp_next->listprev = list_tmp;
else // table->curhashelem 为列表中的最后一个元素
table->lasthashelem = list_tmp;
}
//删除当前元素所指向的内存
if(table->curhashelem->b_need_free_value)
{
free(table->curhashelem->value);
}
free(table->curhashelem);
table->curhashelem = list_tmp_next; // 设置当前元素指向下一个元素
}
else
return HASH_TABLE_NO_CUR_ELEMENT;
return 0;
}
void hash_table_custom_free(key_hash_table *table, free_hashtable_func free_func)
{
if(table == NULL) return;
key_hash_element_t *list_tmp = NULL,*list_tmp_next = NULL;//用于存放hashtable桶的元素:list_tmp从第一个开始,list_tmp_next用于保存下一个元素(用于纵向列表)
/**
* 释放hashtable所有元素的内存空间
*/
list_tmp = table->firsthashelem;
while(list_tmp != 0)//释放元素内存空间
{
key_hash_element_t *hash_tmp,*hash_next_tmp; //用于保存横向hashtable的元素,可能多个不同的key,hash值是一样的,这时候就需要在桶中的同一个元素使用链表标识
hash_tmp = list_tmp->hashnext;//
while(hash_tmp != 0)
{
hash_next_tmp = hash_tmp->hashnext;
free_func(hash_tmp->value);
free(hash_tmp);
hash_tmp = hash_next_tmp;
}
list_tmp_next = list_tmp->listnext;//获取hashtbale的下一个元素
free_func(list_tmp->value);
free(list_tmp);
list_tmp = list_tmp_next;
}
table->firsthashelem = 0;
table->lasthashelem = 0;
table->curhashelem = 0;
for (int i = 0 ; i < CKEYHASHTABLESIZE ; i++)
table->element_table[i] = NULL;
free(table);//释放table自身
}
int hash_table_add_element(key_hash_table* table,const uint32_t key,void *value,int bNeedFreeValue)
{
if(table == NULL) return HASH_TABLE_NO_EXIST;
key_hash_element_t *e,*newelem;
int index = get_hash_table_index(key);//不同的元素可能获取到相同的index
e = table->element_table[index];
int found = 0;
while(!found && e != 0)
{
if (e->key == key)
found = 1;
else
e = e->hashnext;
}
if(!found)
{
//元素key不存在,往hash table中添加新元素
newelem = (key_hash_element_t*)calloc(1, sizeof(key_hash_element_t));
newelem->value = value;
newelem->key = key;
newelem->hashindex = index;
newelem->b_need_free_value = bNeedFreeValue;
if (newelem == 0)
return HASH_TABLE_OUT_MEMORY;
e = table->element_table[index];
table->element_table[index] = newelem;
newelem->hashnext = e;
if (e != 0)
e->hashprev = newelem;
if (table->firsthashelem == 0)// hash列表中还不存在元素
{
table->firsthashelem = newelem;
table->lasthashelem = newelem;
}
else //hash列表中已经存在一些元素
{
table->lasthashelem->listnext = newelem;
newelem->listprev = table->lasthashelem;
table->lasthashelem = newelem;
}
}else
{
if (table->cover_old_value)//替换元素
{
hash_table_goto_key_element(table, key);
table->curhashelem->value = value;
}
else
{
return HASH_TABLE_KEYALREADYEXISTS;
}
}
return 0;
}
int hash_table_goto_key_element(key_hash_table *table,const uint32_t key)
{
if(table == NULL)
return HASH_TABLE_NO_EXIST;
int index = get_hash_table_index(key);//获取下标位置(hash 函数-如何根据key来得到hash的位置)
table->curhashelem = table->element_table[index];
int found = 0;
while(!found && table->curhashelem != 0)
{
if (table->curhashelem->key == key)
found = 1;
else
table->curhashelem = table->curhashelem->hashnext;
}
if (!found)
return HASH_TABLE_NO_KEY_ELEMENT;
return 0;
}
int hash_table_has_key_element(key_hash_table *table, const uint32_t key)
{
if(table == NULL) return 0;
key_hash_element_t *tmp;
int index = get_hash_table_index(key);
tmp = table->element_table[index];
int found = 0;
while(!found && tmp != 0)
{
if (tmp->key == key)
found = 1;
else
tmp = tmp->hashnext;
}
return found;
}
int hash_table_del_key_element(key_hash_table *table, const uint32_t key)
{
if(table == NULL) return HASH_TABLE_NO_EXIST;
int status;
status = hash_table_goto_key_element(table,key);
if (status < 0)
return status;
return hash_table_del_cur_element(table);
}
void hash_table_goto_first_element(key_hash_table *table)
{
if(table != NULL)
table->curhashelem = table->firsthashelem;
}
void hash_table_goto_prev_element(key_hash_table *table)
{
if(table && table->curhashelem)
table->curhashelem = table->curhashelem->listprev;
}
void hash_table_goto_last_element(key_hash_table *table)
{
if(table != NULL)
table->curhashelem = table->lasthashelem;
}
void hash_table_goto_next_element(key_hash_table *table)
{
if(table && table->curhashelem)
table->curhashelem = table->curhashelem->listnext;
}
uint32_t hash_table_get_cur_key(key_hash_table *table)
{
if(table != NULL && table->curhashelem != NULL)
return table->curhashelem->key;
return HASH_TABLE_NO_CUR_KEY;
}
void *hash_table_get_cur_element(key_hash_table *table)
{
if(table != NULL)
return table->curhashelem->value;
return NULL;
}
int hash_table_has_cur_element(key_hash_table *table)
{
if(table != NULL)
return (table->curhashelem == 0)? 0 : 1;
return 0;
}
调用例子
void test_hashtable()
{
struct key_hash_table *hashtable = NULL;
hash_table_new(&hashtable, 1);
int a = 1;
int b = 2 ;
hash_table_add_element(hashtable,1,&a,0);
hash_table_add_element(hashtable,1,&b,0);
hash_table_add_element(hashtable,2,&b,0);
hash_table_add_element(hashtable,3,&a,0);
hash_table_del_key_element(hashtable,1);
hash_table_del_key_element(hashtable,3);
hash_table_del_key_element(hashtable,3);
hash_table_goto_first_element(hashtable);
while(hash_table_has_cur_element(hashtable))
{
int *c = (int *) hash_table_get_cur_element(hashtable);
printf("c::%d\n", *c);
hash_table_goto_next_element(hashtable);
}
hash_table_free(hashtable);
}