百度百科:
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
查找性能
散列表的查找过程基本上和造表过程相同。一些关键码可通过散列函数转换的地址直接找到,另一些关键码在散列函数得到的地址上产生了冲突,需要按处理冲突的方法进行查找。在介绍的三种处理冲突的方法中,产生冲突后的查找仍然是给定值与关键码进行比较的过程。所以,对散列表查找效率的量度,依然用平均查找长度来衡量。
查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素:
1. 散列函数是否均匀;
2. 处理冲突的方法;
3. 散列表的装填因子。
散列表的装填因子定义为:α= 填入表中的元素个数 / 散列表的长度
α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。
实际上,散列表的平均查找长度是装填因子α的函数,只是不同处理冲突的方法有不同的函数。
hash函数获取hash_key举例:MOD为hash桶深度
字符串
(著名的ELFhash算法)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
前面介绍过linux内核hash链表的操作,因为本篇需要用到hash_list或者是DL_list创建hash,所以在这里先贴上hash_list/dl_list的定义及操作的头文件:hash_list.h
#ifndef _DL_LIST_H_
#define _DL_LIST_H_
#ifdef __cplusplus
extern "C"{
#endif
typedef struct tagLIST_NODE
{
struct tagLIST_NODE *next; // 指向下一个结点的指针
struct tagLIST_NODE **pprev;// 指向上一个结点的next指针的地址
}HLIST_NODE_S;
typedef struct tagHLIST_HEAD
{
struct tagHLIST_HEAD *first; // 指向每一个hash桶的第一个结点的指针
}HLIST_HEAD_S;
// 初始化hash桶的头结点
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
// 初始化hash桶的普通结点
static inline void INIT_HLIST_NODE(HLIST_NODE_S *node)
{
node->next = NULL;
node->pprev = NULL;
}
/**
* HLIST_Add_Head
* @n: the element to add to the hash list.
* @h: the list to add to.
*/
static inline void HLIST_AddHead(HLIST_NODE_S *node, HLIST_HEAD_S *head)
{
HLIST_NODE_S *first = head->first;
node->next = first;
if (NULL != first)
{
first->pprev = &node->next;
}
head->first = node;
node->pprev = &head->first;
}
/* next must be != NULL */
/* node:要添加的新的节点。
* next:在next节点之前添加node。
* 在next节点的前面添加一个新的节点n,在使用这个函数中要特别注意,next不能为NULL。
*/
static inline void HLIST_AddBefore(HLIST_NODE_S *node, HLIST_NODE_S *next)
{
node->pprev = next->pprev;
node->next = next;
next->pprev = &node->next;
*(node->pprev) = node;
}
/* next must be != NULL */
/* node:要添加的新的节点。
* next:表示在next节点之后添加node。
* 在next 节点的后面添加一个新的节点node,这里也要求next不能为NULL
*/
static inline void HLIST_AddAfter(HLIST_NODE_S *node, HLIST_NODE_S *next)
{
next->next = node->next;
node->next = next;
next->pprev = &node->next;
if(NULL != next->next)
{
next->next->pprev = &next->next;
}
}
/* node:要删除的节点。
* 对于删除操作的话,要注意node是不是末尾节点,如果是末尾节点的话,next就是NULL?
* 所以就没有指向的pprev,就更不能进行相应的修改了,否则进行修改。
*/
static inline void __hlist_del(HLIST_NODE_S *node)
{
HLIST_NODE_S *next = node->next;
HLIST_NODE_S **pprev = node->pprev;
*pprev = next;
if (NULL != next)
{
next->pprev = pprev;
}
}
/* node:要删除的节点。
* 在这个函数中首先删除了node节点,之后将n节点的两个指针指向了NULL,表示不可使用的地方
*/
static inline void HLIST_Del(HLIST_NODE_S *node)
{
__hlist_del(node);
node->next = NULL;
node->pprev = NULL;
}
/*
* 判断一个结点是否已经存在于hash桶中
* 判断h->prev是不是为空,如果pprev的指向是空的话,表示这个节点没有添加到这个链表当中来,
* 如果是空,返回true,否则返回false
*/
static inline int HLIST_UnHashed(const HLIST_NODE_S *node)
{
return !node->pprev;
}
// 判断一个hash桶是否为空
/* head:struct hlist_head节点指针(hlist链表的头节点)。
* 判断hlist链表是不是空链表,如果是,返回true,否则返回false。
*/
static inline int HLIST_IsEmpty(const HLIST_HEAD_S *head)
{
return (NULL == head->first);
}
/***********************************************************
* 遍历hash链表,需要用到两个地址偏移的宏定义 *
************************************************************/
//获取结构体成员相对于结构体的偏移
#define offsetof(TYPE,MEMBER) ((int) &((TYPE *)0)->MEMBER)
//通过获取结构体中的某个成员,反推该结构体的指针
#define container_of(ptr, type , member) ({ \
const typeof(((type *)0)->member) *__mptr = (ptr) ; \
(type *)((char *)__mptr - offsetof(type,member)) ;})
#define HLIST_ENTRY(ptr, type, member) container_of(ptr, type, member)
/* pos:struct hlist_node类型的一个指针;
* head:struct hlist_head类型的一个指针,表示hlist链表的头结点。
* 这个实际上就是一个for循环,从头到尾遍历链表。
*/
/* 普通遍历,遍历过程中不能删除节点,否则可能会出现后续节点的访问错误 */
#define HLIST_FOR_EACH(pos, head) \
for (pos = (head)->first; {pos != NULL ; 1; }); \
pos = pos->next)
/* 这个实际上就是一个for循环,从头到尾遍历链表。这个和前面的不同的是多了一个n,
* 这么做是为了遍历过程中防止断链的发生。删除时用这个。
* pos:struct hlist_node类型的一个指针;
* next:struct hlist_node类型的一个指针,这里为区分,写作next;
* head:struct hlist_head类型的一个指针,表示hlist链表的头结点。
这是安全遍历,实际上就是预读取,提前指向下一个节点,遍历过程中可以删除节点
*/
#define HLIST_FOR_EACH_SAFE(pos, next, head) \
for (pos = (head)->first; pos && ({ next = pos->next; 1; }); \
pos = next)
/* ptype:用来存放遍历到的数据结构的地址,类型是type *;
* pos:struct hlist_node类型的一个指针;
* head:hlist链表的头结点;
* member:struct hlist_node在type结构体中的变量的名称。
* 通过hlist_entry 这个宏,我们可以访问hash链表所在结构体的其他成员,很多时候需要这样访问。
本遍历宏并非安全定义,遍历过程中不能删除节点
*/
/**
* HLIST_FOR_EACH_ENTRY - iterate over list of given type
* @ptype: the type * to use as a loop cursor.
* @pos: the &HLIST_NODE_S to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the hlist_node within the struct.
*/
#define HLIST_FOR_EACH_ENTRY(ptype, pos, head, member) \
for (pos = (head)->first; \
(pos != NULL) && \
({ ptype = HLIST_ENTRY(pos, typeof(*ptype), member); 1;}); \
pos = pos->next)
/* ptype:用来存放遍历到的数据结构的地址,类型是type *;
* pos:struct hlist_node类型的一个指针;
* n:struct hlist_node类型的一个指针;
* head:hlist链表的头结点;
* member:struct hlist_node在type结构体中的变量的名称。
* 在循环中,我们就可以使用tops来指向type
* 类型结构体的任何一个变量了。这个宏函数也是为了防止在遍历的时候删除节点而引入的。
*/
/**
* HLIST_FOR_EACH_ENTRY_SAFE - iterate over list of given type safe against
removal of list entry
* @tpos: the type * to use as a loop cursor.
* @pos: the &HLIST_NODE_S to use as a loop cursor.
* @n: another &HLIST_NODE_S to use as temporary storage
* @head: the head for your list.
* @member: the name of the hlist_node within the struct.
*/
#define HLIST_FOR_EACH_ENTRY_SAFE(ptype, pos, next, head, member) \
for (pos = (head)->first; \
pos && ({ next = pos->next; 1; }) && \
({ ptype = HLIST_ENTRY(pos, typeof(*ptype), member); 1;}); \
pos = next)
#ifndef __cplusplus
}
#endif // end of__cplusplus
#endif // end of _DL_LIST_H_
根据以上结构,我们创建hash表,首先为了便于区分,我们先重命名一下HLIST_HEAD_S 和HLIST_NODED_S这两个在上面头文件中定义的结构:分别为HASH_LIST_S,HASH_NODE_S。
1.定义hash结构及访问的宏定义:
hash结构是以上文的头文件定义的hash_list重命名后定义的,访问宏定义也用到以上的访问宏,所以在看hash_table结构及访问的时候,需要结合上半部分头文件看,上边的头文件就是之前的hash-list一文中介绍的,当中纠正一个括号不对称的错误。
#ifndef ULONG
#define ULONG unsigned long
#endif
#ifndef LONG
#define LONG long
#endif
typedef HLIST_NODE_S HASH_NODE_S;/* hash table node */
typedef HLIST_HEAD_S HASH_LIST_S;/* hash config list */
typedef struct Hash_Table
{
ULONG ulSize;
ULONG (*pfHash)(const void *);/* hash function */
HASH_LIST_S *pstBckt;
}HASH_TABLE_S;
#define HASH_GET_INDEX(pstTbl, pKey) ((pstTbl)->pfHash(pKey))
#define HASH_IS_VALID_INDEX(pstTbl, ulIndex) ((ulIndex) < (pstTbl)->ulSize)
#define HASH_GET_LIST(pstTbl, ulIndex) (&(pstTbl)->pstBckt[ulIndex])
#define HASH_ENTRY(ptr, type, member) container_of(ptr, type, member)
#define HASH_TBL_FOREACH(pstTbl, ulIndex) \
for((ulIndex) = 0; (ulIndex) < (pstTbl)->ulSize; (ulIndex)++)
#define HASH_BUCKT_FOREACH(pstList, pstNode) \
HLIST_FOR_EACH(pstNode, pstList)
#define HASH_BUCKT_FOREACH_SAFE(pstList, pstNode, pstNext) \
HLIST_FOR_EACH_SAFE(pstNode, pstNext, pstList)
#define HASH_SCAN_BUCKT(pstTbl, ulIndex, pstNode) \
HASH_BUCKT_FOREACH(HASH_GET_LIST(pstTbl, ulIndex), pstNode)
#define HASH_SCAN_BUCKT_SAFE(pstTbl, ulIndex, pstNode, pstNext) \
HASH_BUCKT_FOREACH_SAFE(HASH_GET_LIST(pstTbl, ulIndex), pstNode, pstNext)
#define HASH_SCAN_TBL(pstTbl, ulIndex, pstNode) \
HASH_TBL_FOREACH(pstTbl, ulIndex) \
HASH_SCAN_BUCKT(pstTbl, ulIndex, pstNode)
#define HASH_SCAN_TBL_SAFE(pstTbl, ulIndex, pstNode, pstNext) \
HASH_TBL_FOREACH(pstTbl, ulIndex) \
HASH_SCAN_BUCKT_SAFE(pstTbl, ulIndex, pstNode, pstNext)
2.创建hash:
这里需要传入获取hash index 的函数,作为hash索引,函数根据需要的key值计算所添加的节点所在hash桶的索引,然后添加到该索引对应的链表中:创建hash主要是申请资源并做初始化工作。
/* callback is hash function to get hash index */
HASH_TABLE_S * HASH_Create(ULONG ulSize, ULONG (*pfHash)(const void *))
{
HASH_TABLE_S *pstTbl = NULL;
ULONG ulLen = 0, i = 0;
if (0 == ulSize || NULL == pfHash)
{
return (HASH_TABLE_S *)NULL;
}
ulLen= sizeof(HASH_TABLE_S) + sizeof(HASH_LIST_S) * ulSize;
pstTbl = (HASH_TABLE_S *)malloc(ulLen);
if (NULL != pstTbl)
{
pstTbl->pstBckt = (HASH_LIST_S *)(pstTbl +1);
pstTbl->ulSize = ulSize;
pstTbl->pfHash = pfHash;
for(i = 0; i < ulSize; i++)
{
INIT_HLIST_HEAD(pstTbl->pstBckt[i]);
}
}
return pstTbl;
}
3.添加hash节点:
其实都是用的hash_list节点的添加函数:具体函数见上文中的hash_list.h
#define HASH_Add_List(pstList, pstNode) \
HLIST_Add_Head(pstNode, pstList)
void HASH_Add(HASH_TABLE_S *pstTbl, HASH_NODE_S *pstNode, const void *pKey)
{
HASH_LIST_S *pstList = NULL;
ULONG ulIndex = 0;
ulIndex = HASH_GET_INDEX(pstTbl, pKey);
pstList = HASH_GET_LIST(pstTbl, ulIndex);
HASH_Add_List(pstList, pstNode);
return;
}
void HASH_ListAddAfter(HASH_LIST_S * pstList, HASH_NODE_S *pstPrev,
HASH_NODE_S *pstInst)
{
if (NULL == pstPrev)
{
HLIST_AddHead((HASH_LIST_NODE_S *)pstInst , (HASH_LIST_HEAD_S *) pstList);
}
else
{
HLIST_AddAfter((HASH_LIST_NODE_S *)pstInst, (HASH_LIST_NODE_S *)pstList);
}
return ;
}
void HASH_AddOrder(HASH_TABLE_S *pstTbl, HASH_NODE_S *pstNode,
LONG (*pfKeyCmp)(const HASH_NODE_S*, const void*),
const void *pKey)
{
HASH_LIST_S *pstList = NULL;
HASH_NODE_S *pstPrevNode = (HASH_NODE_S *)NULL;
HASH_NODE_S pstLoopNode = NULL;
ULONG ulIndex = 0;
ulIndex = HASH_GET_INDEX(pstTbl, pKey);
pstList = HASH_GET_LIST(pstTbl, ulIndex);
HASH_SCAN_BUCKT(pstTbl, ulIndex, pstLoopNode)
{
if (pfKeyCmp(pstLoopNode, pKey))
{
break;
}
pstPrevNode = pstLoopNode;
}
HASH_ListAddAfter(pstList, pstPrevNode, pstNode);
return;
}
4.删除hash节点:
用的依然是hash_list.h的已有函数
void HASH_Del(HASH_NODE_S *pstNode)
{
HLIST_Del(pstNode);
}
void HASH_Free(HASH_NODE_S *pstNode)
{
free(pstNode);
}
5.释放hash tale:
删除全部节点并释放,释放函数可以自定义:遍历hash table的宏定义用在前面
/* pfFree = HASH_FreeNode */
void HASH_Destroy(HASH_TABLE_S *pstTbl, void (*pfFree)(HASH_NODE_S*))
{
HASH_NODE_S *pstNode = NULL;
HASH_NODE_S *pstNext = NULL;
ULONG ulIndex = 0;
if (NULL == pstTbl)
{
return ;
}
if (NULL != pfFree)
{
HASH_TBL_FOREACH(pstTbl, ulIndex)
{
/* safe to del */
HASH_SCAN_BUCKT_SAFE(pstTbl, ulIndex, pstNode, pstNext)
{
(*pfFree)(pstNode);
}
}
}
free((HASH_TABLE_S *)pstTbl);
return;
}
6.最后,查找hash
查找函数自定义:
HASH_NODE_S * HASH_Find(HASH_TABLE_S *pstTbl, const void * pKey,
LONG (*pfKeyCmp)(const HASH_NODE_S *, const void *))
{
HASH_NODE_S *pstNode = NULL;
ULONG ulIndex = 0;
if ((NULL == pstTbl) || (NULL == pKey) || (NULL == pfKeyCmp))
{
return (HASH_NODE_S *)NULL;
}
ulIndex = HASH_GET_INDEX(pstTbl, pKey);
if (!HASH_IS_VALID_INDEX(pstTbl, ulIndex))
{
return (HASH_NODE_S *)NULL;
}
HASH_SCAN_BUCKT(pstTbl, ulIndex, pstNode)
{
if (0 == pfKeyCmp(pstNode, pKey))
{
break;
}
}
return pstNode;
}
完整的hash_table.c文件如下:并没有编译过
#ifdef __cplusplus
extern "C"{
#endif
#include "dl_list.h"
#ifndef ULONG
#define ULONG unsigned long
#endif
#ifndef LONG
#define LONG long
#endif
typedef HLIST_NODE_S HASH_NODE_S;/* hash table node */
typedef HLIST_HEAD_S HASH_LIST_S;/* hash config list */
typedef struct Hash_Table
{
ULONG ulSize;
ULONG (*pfHash)(const void *);/* hash function */
HASH_LIST_S *pstBckt;
}HASH_TABLE_S;
#define HASH_GET_INDEX(pstTbl, pKey) ((pstTbl)->pfHash(pKey))
#define HASH_IS_VALID_INDEX(pstTbl, ulIndex) ((ulIndex) < (pstTbl)->ulSize)
#define HASH_GET_LIST(pstTbl, ulIndex) (&(pstTbl)->pstBckt[ulIndex])
#define HASH_ENTRY(ptr, type, member) container_of(ptr, type, member)
#define HASH_TBL_FOREACH(pstTbl, ulIndex) \
for((ulIndex) = 0; (ulIndex) < (pstTbl)->ulSize; (ulIndex)++)
#define HASH_BUCKT_FOREACH(pstList, pstNode) \
HLIST_FOR_EACH(pstNode, pstList)
#define HASH_BUCKT_FOREACH_SAFE(pstList, pstNode, pstNext) \
HLIST_FOR_EACH_SAFE(pstNode, pstNext, pstList)
#define HASH_SCAN_BUCKT(pstTbl, ulIndex, pstNode) \
HASH_BUCKT_FOREACH(HASH_GET_LIST(pstTbl, ulIndex), pstNode)
#define HASH_SCAN_BUCKT_SAFE(pstTbl, ulIndex, pstNode, pstNext) \
HASH_BUCKT_FOREACH_SAFE(HASH_GET_LIST(pstTbl, ulIndex), pstNode, pstNext)
#define HASH_SCAN_TBL(pstTbl, ulIndex, pstNode) \
HASH_TBL_FOREACH(pstTbl, ulIndex) \
HASH_SCAN_BUCKT(pstTbl, ulIndex, pstNode)
#define HASH_SCAN_TBL_SAFE(pstTbl, ulIndex, pstNode, pstNext) \
HASH_TBL_FOREACH(pstTbl, ulIndex) \
HASH_SCAN_BUCKT_SAFE(pstTbl, ulIndex, pstNode, pstNext)
/* callback is hash function to get hash index */
HASH_TABLE_S * HASH_Create(ULONG ulSize, ULONG (*pfHash)(const void *))
{
HASH_TABLE_S *pstTbl = NULL;
ULONG ulLen = 0, i = 0;
if (0 == ulSize || NULL == pfHash)
{
return (HASH_TABLE_S *)NULL;
}
ulLen= sizeof(HASH_TABLE_S) + sizeof(HASH_LIST_S) * ulSize;
pstTbl = (HASH_TABLE_S *)malloc(ulLen);
if (NULL != pstTbl)
{
pstTbl->pstBckt = (HASH_LIST_S *)(pstTbl +1);
pstTbl->ulSize = ulSize;
pstTbl->pfHash = pfHash;
for(i = 0; i < ulSize; i++)
{
INIT_HLIST_HEAD(pstTbl->pstBckt[i]);
}
}
return pstTbl;
}
#define HASH_Add_List(pstList, pstNode) \
HLIST_Add_Head(pstNode, pstList)
void HASH_Add(HASH_TABLE_S *pstTbl, HASH_NODE_S *pstNode, const void *pKey)
{
HASH_LIST_S *pstList = NULL;
ULONG ulIndex = 0;
ulIndex = HASH_GET_INDEX(pstTbl, pKey);
pstList = HASH_GET_LIST(pstTbl, ulIndex);
HASH_Add_List(pstList, pstNode);
return;
}
void HASH_ListAddAfter(HASH_LIST_S * pstList, HASH_NODE_S *pstPrev,
HASH_NODE_S *pstInst)
{
if (NULL == pstPrev)
{
HLIST_AddHead((HASH_LIST_NODE_S *)pstInst , (HASH_LIST_HEAD_S *) pstList);
}
else
{
HLIST_AddAfter((HASH_LIST_NODE_S *)pstInst, (HASH_LIST_NODE_S *)pstList);
}
return ;
}
void HASH_AddOrder(HASH_TABLE_S *pstTbl, HASH_NODE_S *pstNode,
LONG (*pfKeyCmp)(const HASH_NODE_S*, const void*),
const void *pKey)
{
HASH_LIST_S *pstList = NULL;
HASH_NODE_S *pstPrevNode = (HASH_NODE_S *)NULL;
HASH_NODE_S pstLoopNode = NULL;
ULONG ulIndex = 0;
ulIndex = HASH_GET_INDEX(pstTbl, pKey);
pstList = HASH_GET_LIST(pstTbl, ulIndex);
HASH_SCAN_BUCKT(pstTbl, ulIndex, pstLoopNode)
{
if (pfKeyCmp(pstLoopNode, pKey))
{
break;
}
pstPrevNode = pstLoopNode;
}
HASH_ListAddAfter(pstList, pstPrevNode, pstNode);
return;
}
void HASH_Del(HASH_NODE_S *pstNode)
{
HLIST_Del(pstNode);
}
void HASH_Free(HASH_NODE_S *pstNode)
{
free(pstNode);
}
/* pfFree = HASH_FreeNode */
void HASH_Destroy(HASH_TABLE_S *pstTbl, void (*pfFree)(HASH_NODE_S*))
{
HASH_NODE_S *pstNode = NULL;
HASH_NODE_S *pstNext = NULL;
ULONG ulIndex = 0;
if (NULL == pstTbl)
{
return ;
}
if (NULL != pfFree)
{
HASH_TBL_FOREACH(pstTbl, ulIndex)
{
/* safe to del */
HASH_SCAN_BUCKT_SAFE(pstTbl, ulIndex, pstNode, pstNext)
{
(*pfFree)(pstNode);
}
}
}
free((HASH_TABLE_S *)pstTbl);
return;
}
HASH_NODE_S * HASH_Find(HASH_TABLE_S *pstTbl, const void * pKey,
LONG (*pfKeyCmp)(const HASH_NODE_S *, const void *))
{
HASH_NODE_S *pstNode = NULL;
ULONG ulIndex = 0;
if ((NULL == pstTbl) || (NULL == pKey) || (NULL == pfKeyCmp))
{
return (HASH_NODE_S *)NULL;
}
ulIndex = HASH_GET_INDEX(pstTbl, pKey);
if (!HASH_IS_VALID_INDEX(pstTbl, ulIndex))
{
return (HASH_NODE_S *)NULL;
}
HASH_SCAN_BUCKT(pstTbl, ulIndex, pstNode)
{
if (0 == pfKeyCmp(pstNode, pKey))
{
break;
}
}
return pstNode;
}
#ifndef __cplusplus
}
#endif // end of__cplusplus
参考文档: