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

来源:互联网 发布:php面试题大全 编辑:程序博客网 时间:2024/06/06 09:51

在一个老牌通信公司工作几年,做的核心网项目开发,整天关注的是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 listGEN_NODE_Phead;/// pointer to the head node of this listGEN_NODE_Ptail;/// pointer to the tail node of this listintnumObjects;/// 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表的操作中套用双向链表的操作方法。

0 0
原创粉丝点击