您的位置:首页 > 其它

哈希表+双向链表的组合使用

2016-03-24 13:47 435 查看
在一个老牌通信公司工作几年,做的核心网项目开发,整天关注的是call flow,所参与的项目基本和MM和Call Control相关,

大多重点在项目开发逻辑,以及信令的解析和分析上。

公司内部代码有很多优秀的设计细节,和架构方式,只不过平时在此宏观的架构的基础上添加新代码,使用优秀简洁的接口,很少

仔细分析这些代码的优势所在。其中包括网络通信,进程间通信,以及多线程,共享内存,进程管理,状态机,以及定时器,

各种log机制。

最近有了一些闲暇时间,准备细细阅读基础代码,将一些优秀的设计理念和方法,加以整理总结。

首先看一下常用的hash表与双向链表结组合使用,这样的组合一般使用在网络通讯中,接收端收到大量的新数据消息,

将消息先存储到hash中,查询快捷,方便。

哈希表是由一个表头加上若干个节点组成,每个节点都是一个双向链表,数据对象实际是存储在哈希表节点的双向链表节点中。

由于每个节点都是一个双向链表,链表的每个节点中存储数据。

这样的好处是,如果随机产生的hash值如果相同,也可以使用,也不用重新产生命中的相同的hash值,可以直接存入对应的双向链表中。

这需要一个很好的散列化的hash函数,将数据对象均匀分布到节点的链表中。

哈希表的表头中定义了:

1. 哈希表的节点的最大个数变量

2. 产生哈希值的哈希函数指针变量的定义

3. 用于查找的对比函数指针变量的定义

4. 定义存储节点的数组

5. 存储数据对象的个数

双向链表定义:

1. 带有双向指针的表头

2. 带有双向指针的表尾

3. 带有双向链表中存储数据的个数

哈希表的表头定义如下:

/**
* data structure of hash table
*/
typedef struct _HASHTABLE
{
/// static configurable data of hash table
int             numBuckets;     /// number of bucket in this hash table
HASHFUNCPTR     hashFunc;       /// hash function from key to bucket
COMPFUNCPTR     cmpFunc;        /// comparison function to the key of the object
DLIST_P         bucketArray;    /// array of buckets in this hash table

/// dynamic data of hash table
int             numObjects;     /// number of objects in this hash table
} HASHTABLE_T, *HASHTABLE_P;


双向链表的定义如下:

/**
*
* The double linked list is used to manage the list of objects for specific purpose.
* It allows the user to insert the object into or remove it from the list dynamically.
*
* To link them together, the object MUST contain GEN_NODE_T as the first element in its
* structure, so that we can cast the pointer to the object to the GEN_NODE_P.
*
* The structure of double linked list looks like the following.
*
*		+----------+         +----------+                         +----------+
*     NULL <---+-- prev   |<--------+-- prev   |<--------       <--------+-- prev   |
*		+----------+         +----------+           ...           +----------+
*		|   next --+-------->|   next --+-------->       -------->|   next --+--->NULL
*		+----------+         +----------+                         +----------+
*		|   data   |         |   data   |                         |   data   |
*		+----------+         +----------+                         +----------+
*		     ^                                                         ^
*        	     |                                                         |
*   		     |                                                         |
*  		 head(list)                                                tail(list)
*
*/

/**
* data structure of double linked list
*/
typedef struct _DLIST
{
/// dynamic data of this list
GEN_NODE_P	head;		/// pointer to the head node of this list
GEN_NODE_P	tail;		/// pointer to the tail node of this list
int		numObjects;	/// current number of nodes in this list
} DLIST_T, *DLIST_P;


/**
* data structure of general purpose node used in double linked list
*
*/
typedef struct _GEN_NODE {
struct _GEN_NODE        *prev;          /// pointer to the previous node in the list
struct _GEN_NODE        *next;          /// pointer to the next node in the list
} GEN_NODE_T, *GEN_NODE_P;


哈希表的创建:创建函数中有带入两个函数指针类型的函数,一个是hash函数,一个是对比函数,在头文件中事先定义好对应的函数指针。

哈希函数指针的定义

typedef unsigned int    (* HASHFUNCPTR)(const void *key);
对比函数指针的定义
typedef int     (* COMPFUNCPTR)(const void * obj, const void *key);


HASHTABLE_P HashTable_create(int numBuckets, HASHFUNCPTR hashFunc, COMPFUNCPTR cmpFunc)
{
HASHTABLE_P ht = NULL;

assert((hashFunc != NULL) && (cmpFunc != NULL) && (numBuckets > 0));

ht = (HASHTABLE_P)calloc(1, sizeof(HASHTABLE_T) + sizeof(DLIST_T) * numBuckets);

if (ht != NULL) {
ht->numBuckets = numBuckets;
ht->hashFunc = hashFunc;
ht->cmpFunc = cmpFunc;
ht->bucketArray = (DLIST_P)(ht + 1);
}

return ht;
}

这个函数是创建整个哈希表,为哈希表申请内存空间,包括哈希表的表头,还同时创建了numBuckets个双向链表。

在此用的申请空间的函数是calloc,在申请完空间后,会自动将空间清空。这是一个好的使用方法。

将哈希函数和对比函数的函数指针,赋入函数。

具体使用方法如下:

/* Max Number of Active CSFB CALL in Queue */
#define MAX_CSFB_CALL           4096


g_pPgrHash  = HashTable_create(MAX_CSFB_CALL, s102PgrHash, s102PgrCmp);</span>
if (g_pPgrHash  == NULL)
{
UX_ID_ELOG(ES1020623, "Unable to HashTable_create g_pPgrHash \n");
UXTPCU(A21_KILL_SIG);
}


销毁整个哈希表:销毁整个hash表,可以先调用清空此哈希表中所有数据的值,然后将申请的hash表的内存空间free掉。

void HashTable_destroy(HASHTABLE_P ht, FREEFUNCPTR freeFunc)
{
assert(ht != NULL);

// delete all the object from each bucket
HashTable_delAllObjs(ht, freeFunc);

// release the memory for hashtable
free(ht);

return;
}


哈希表插入数据:
可以根据查找函数HashTable_getBucket(),此函数的功能是根据key值,调用hash函数,产生hash值,返回对应数组中双向链表的地址。
然后调用双向链表的插入函数,将数据加入到双向链表的表尾处。

int HashTable_addObj(HASHTABLE_P ht, void *key, void *obj)
{
DLIST_P bucket = NULL;

assert((ht != NULL) && (key != NULL) && (obj != NULL));

bucket = HashTable_getBucket(ht, key);
if (bucket == NULL) return HASHTABLE_ERROR_HASHFAIL;

if (DList_addObjTail(bucket, obj) != DLIST_NO_ERROR) {
return HASHTABLE_ERROR_INSERTFAIL;
}

ht->numObjects++;
return HASHTABLE_NO_ERROR;
}


双向链表添加数据的函数如下:

int DList_addObjTail(DLIST_P list, void *obj)
{
assert((list != NULL) && (obj != NULL));

GEN_NODE_P node = (GEN_NODE_P)obj;
node->prev = list->tail;
node->next = NULL;

if (list->tail == NULL) {
if ((list->head != NULL) || (list->numObjects != 0)) {
return DLIST_ERROR_INCONSISTENT;
}

list->head = node;
} else {
node->prev->next = node;
}

list->tail = node;
list->numObjects++;

return DLIST_NO_ERROR;
}


OBJ即为插入的数据对象,此数据对象有个要求,此数据结构的第一个变量必须是GEN_NODE_T link;

双向链表可以从head往里插入数据,也可从tail处插入数据。

此次是从tail处插入数据。

1. 首先将数据对象的首地址强制转换成节点类型,这就是为什么数据对象的结构体的第一个变量为什么必须是GEN_NODE_T 类型的缘故,将数据前后链接。

2. 从结尾处插入数据,将此节点的前驱指向双向链表的tail,将此节点的后继设为空。

3. 由于是双向链表,前一个数据,需要将后继改为新加入的节点,如果此链表为空,可直接将链表的头指向此数据对象节点。

4.从结尾出插入数据,新加入的数据即为tail,将链表的tail指向新加入的数据节点

5. 将链表中数据对象的个数加1

使用方法如下:

// add this csr into hash table with given key
retVal = HashTable_addObj(g_pCsrHash, (void *)mnidPtr, (void *)&g_s102csr[objId]);

if (retVal != HASHTABLE_NO_ERROR) {
UX_ID_ELOG( ES1020576, "Failed to call HashTable_addObj to add the csr index %d : retval: %d", objI\
d, retVal);
/// free the Correlation Id for other request message
Allocator_freeObject(g_pCsrIdleList, objId);
index = -1;
}


哈希表查找数据:

void *HashTable_findObj(HASHTABLE_P ht, void *key, DLIST_P *bucket)
{
DLIST_P bkt = NULL;
void    *obj = NULL;

assert((ht != NULL) && (key != NULL));

bkt = HashTable_getBucket(ht, key);
if (bkt != NULL) {
obj = DList_findObj(bkt, ht->cmpFunc, key);
}

if (bucket != NULL) *bucket = bkt;

return obj;
}


根据查找函数HashTable_getBucket 获取key值应该放得节点位置

根据key值,调用hash函数,产生hash值,返回对应数组中双向链表的地址。

根据对比key,在此双向链表中查询此对象key值,如果找到,返回对应节点的地址。
根据节点地址,可获取此节点中对象的数据。

从双向链表中查找数据的函数如下:

void* DList_findObj(DLIST_P list, COMPFUNCPTR cmpFunc, void *key)
{
assert((list != NULL) && (cmpFunc != NULL) && (key != NULL));

GEN_NODE_P      node = list->head;
int             count = 0;

while ((node != NULL) && (count < list->numObjects)) {
if ((* cmpFunc)((void *)node, key) == 0) return (void *)node;
count++;
node = node->next;
}

return NULL;
}
通过对比函数将key值和链表中的数据key值进行对比,返回节点地址。

哈希表删除一条数据:

void *HashTable_removeObj(HASHTABLE_P ht, void *key)
{
DLIST_P bucket = NULL;
void    *obj = NULL;

assert((ht != NULL) && (key != NULL));

obj = HashTable_findObj(ht, key, &bucket);

if (obj != NULL) {
if (bucket != NULL) {
DList_removeObj(bucket, obj);
ht->numObjects--;
} else {
return NULL;
}
}

return obj;
}

在此哈希表中删除一条数据包含了哈希表查找数据,然后再双向链表中将此数据删除,hash表中数据总个数减1。

双向链表删除一个对象的函数如下:

void DList_removeObj(DLIST_P list, void *obj)
{
assert(list != NULL);

GEN_NODE_P node = (GEN_NODE_P)obj;

if (node == NULL) return;

if (node->prev == NULL) {
// if the object is the head of list, set the head to the next object
list->head = node->next;
} else {
// otherwise, let the previous one links to the next object
node->prev->next = node->next;
}

if (node->next == NULL) {
// if the object is the tail of list, set the tail to the previous object
list->tail = node->prev;
} else {
// otherwise, let the next one links to the previous object
node->next->prev = node->prev;
}

// clear the linkage of this object
node->prev = NULL;
node->next = NULL;

list->numObjects--;
return;
}


删除一个对象的流程如下:

1. 首先将数据对象的首地址强制转换成节点类型,这就是为什么数据对象的结构体的第一个变量为什么必须是GEN_NODE_T 类型的缘故,将数据前后链接,便于操作。

2. 如果要删除的节点的前驱是空,表明此节点为head,将此节点的后继设为head即可

3. 如果要删除的节点的前驱不是空,将此节点前驱的后继指向此节点的后继。

4. 如果要删除的节点的后继是空,表明此节点为tail,将此节点的前驱设为tail即可

5. 如果要删除的节点的后继不是空,将此节点后继的前驱设为此节点的前驱

6. 将此节点前驱后继,设为null

7.链表中的数据总个数减1

清空整个哈希表:

void HashTable_delAllObjs(HASHTABLE_P ht, FREEFUNCPTR freeFunc)
{
int i;

assert(ht != NULL);

for (i=0; i<ht->numBuckets; i++) {
DList_delAllObjs(&ht->bucketArray[i], freeFunc);
}

ht->numObjects = 0;

return;
}


获取哈希表中存储数据对象的个数:

int HashTable_getSize(HASHTABLE_P ht)
{
assert(ht);

if (ht->numObjects < 0) return HASHTABLE_ERROR_INCONSISTENT;
return ht->numObjects;
}


根据key值获取哈希表数据节点的位置:

DLIST_P HashTable_getBucket(HASHTABLE_P ht, void *key)
{
int slot = 0;

assert((ht != NULL) && (key != NULL));

slot = (* ht->hashFunc)(key);
if ((slot < 0) || (slot >= ht->numBuckets)) return NULL;

return &ht->bucketArray[slot];
}


根据key值,调用hash函数,产生hash值,返回对应数组中双向链表的地址。

以上为hash表的操作中套用双向链表的操作方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: