linux数据结构和uthash,UThash 的数据结构

简介:

由于项目的需要,需要在一个嵌入式平台(用C语言)上用到hash map这个数据结构。于是搜索到开源的Uthash。Uthash 是一个C语言开发的hash map工具。其特点是用宏定义了所需要的对map的基本操作,如 插入、删除、查找和遍历。对应地,在uthash中采用 HASH_ADD、HASH-DELETE、HASH_FIND和HASH_ITER宏来操作,非常方便。 Uthash的高级功能也很好用,比如实现一个数据结构对应两个hash map等等,对于int型和string类型也有更简化的操作。对于Uthash的其他特性和使用方法在此不再加以赘述,请参考Uthash的指导文档,写得非常详细。下面仅仅介绍一下Uthash的数据结构。

关于uthash的数据结构,我起初是参考:

http://blog.csdn.net/devilcash/article/details/7230733

但是在仔细看了源代码之后发现这个博客的解释并不完全正确(可能是对应的版本不一样)。下面是我自己的解释(我采用的版本是1.9.8)。参考图见本文的最后部分。建议先看代码再看下图,更能加深理解。

数据结构:

view plain copy

//Uthash的三个数据结构:

//1. UT_hash_bucket作用提供根据hash进行索引。

typedefstructUT_hash_bucket {

structUT_hash_handle *hh_head;

unsigned count;

unsigned expand_mult;

} UT_hash_bucket;

//2. UT_hash_table可以看做hash表的表头。

typedefstructUT_hash_table {

UT_hash_bucket *buckets;

unsigned num_buckets, log2_num_buckets;

unsigned num_items;

structUT_hash_handle *tail;/* tail hh in app order, for fast append    */

ptrdiff_thho;/* hash handle offset (byte pos of hash handle in element */

unsigned ideal_chain_maxlen;

unsigned nonideal_items;

unsigned ineff_expands, noexpand;

uint32_t signature; /* used only to find hash tables in external analysis */

#ifdef HASH_BLOOM

uint32_t bloom_sig; /* used only to test bloom exists in external analysis */

uint8_t *bloom_bv;

charbloom_nbits;

#endif

} UT_hash_table;

//3. UT_hash_handle,用户自定义数据必须包含的结构。

typedefstructUT_hash_handle {

structUT_hash_table *tbl;

void*prev;/* prev element in app order      */

void*next;/* next element in app order      */

structUT_hash_handle *hh_prev;/* previous hh in bucket order    */

structUT_hash_handle *hh_next;/* next hh in bucket order        */

void*key;/* ptr to enclosing struct's key  */

unsigned keylen;                  /* enclosing struct's key len     */

unsigned hashv;                   /* result of hash-fcn(key)        */

} UT_hash_handle;

分析:

重要的是hash_handle的结构体,注意里面的四个指针:

[cpp]  view plain copy

void*prev;/* prev element in app order      */

void*next;/* next element in app order      */

structUT_hash_handle *hh_prev;/* previous hh in bucket order    */

structUT_hash_handle *hh_next;/* next hh in bucket order        */

注意其注释。那么可以看到实际上在uthash中维护了两个链表,一个是app的链表,可以推测应该是按照插入的顺序连起来的。另外一个是和bucket相关的,同样可以推测该bucket应该就是UT_hash_bucket的bucket。

可以根据 HASH_ADD()操作来分析这个数据结构的用法。上面参考文章的图除了在上述四个指针处出错外,其他地方是正确的。

[cpp]  view plain copy

#define HASH_ADD(hh,head,fieldname,keylen_in,add) \

HASH_ADD_KEYPTR(hh,head,&((add)->fieldname),keylen_in,add)

[cpp]  view plain copy

#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \

do{ \

unsigned _ha_bkt; \

(add)->hh.next = NULL; \

(add)->hh.key = (char*)keyptr; \

(add)->hh.keylen = (unsigned)keylen_in; \

if(!(head)) { \

head = (add); \

(head)->hh.prev = NULL; \

HASH_MAKE_TABLE(hh,head); \

} else{ \

(head)->hh.tbl->tail->next = (add); \

(add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \

(head)->hh.tbl->tail = &((add)->hh); \

} \

(head)->hh.tbl->num_items++; \

(add)->hh.tbl = (head)->hh.tbl; \

HASH_FCN(keyptr,keylen_in, (head)->hh.tbl->num_buckets, \

(add)->hh.hashv, _ha_bkt); \

HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt],&(add)->hh); \

HASH_BLOOM_ADD((head)->hh.tbl,(add)->hh.hashv); \

HASH_EMIT_KEY(hh,head,keyptr,keylen_in); \

HASH_FSCK(hh,head); \

} while(0)

对于每个用户定义的hash_map来说,包含N个用户建立的表项。在第一次HASH_ADD的时候, 内部进行了初始化(内部调用了HASH_MAKE__TABLE()操作),通过调用uthash_malloc函数, 该操作分配了1个 UT_hash_table结构体的空间,然后分配了 32 个UT_hash_bucket的空间(起始默认是32个bucket,可以通过修改宏

[cpp]  view plain copy

HASH_INITIAL_NUM_BUCKETS

来修改)。

代码是:

[cpp]  view plain copy

#define HASH_MAKE_TABLE(hh,head) \

do{ \

(head)->hh.tbl = (UT_hash_table*)uthash_malloc( \

sizeof(UT_hash_table)); \

if(!((head)->hh.tbl)) { uthash_fatal("out of memory"); } \

memset((head)->hh.tbl, 0, sizeof(UT_hash_table)); \

(head)->hh.tbl->tail = &((head)->hh); \

(head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \

(head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \

(head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \

(head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \

HASH_INITIAL_NUM_BUCKETS*sizeof(structUT_hash_bucket)); \

if(! (head)->hh.tbl->buckets) { uthash_fatal("out of memory"); } \

memset((head)->hh.tbl->buckets, 0, \

HASH_INITIAL_NUM_BUCKETS*sizeof(structUT_hash_bucket)); \

HASH_BLOOM_MAKE((head)->hh.tbl); \

(head)->hh.tbl->signature = HASH_SIGNATURE; \

} while(0)

回到 HASH_ADD_KEYPTR中去,

分析前面部分,可以看出新建的项add 是通过 prev 和next连接进app链表关系的,也不难看出是插入到链表的后面的。对于每个新建项来说,其

[cpp]  view plain copy

(add)->hh.tbl = (head)->hh.tbl;

所以每个新建项的 tbl字段都指向 HASH_MAKE_TABLE中创建的UT_hash_table结构体。

注意UT_hash_table结构体有个字段是tail, 从HASH_ADD_KEYPTR中可以看出,这个tail总是指向app链表的尾部节点。

有tail的好处不言而喻就是为了往链表的尾部插入新节点更方便。

下面分析HASH_ADD_KEYPTR内部的HASH_ADD_TO_BKT():

[cpp]  view plain copy

/* add an item to a bucket */

#define HASH_ADD_TO_BKT(head,addhh) \

do{ \

head.count++; \

(addhh)->hh_next = head.hh_head; \

(addhh)->hh_prev = NULL; \

if(head.hh_head) { (head).hh_head->hh_prev = (addhh); } \

(head).hh_head=addhh; \

if(head.count >= ((head.expand_mult+1) * HASH_BKT_CAPACITY_THRESH) \

&& (addhh)->tbl->noexpand != 1) { \

HASH_EXPAND_BUCKETS((addhh)->tbl); \

} \

} while(0)

后半部分是bucket扩展的部分,类似于很多容器的扩容操作,对此不做详细分析。仅分析前半部分。

[cpp]  view plain copy

(addhh)->hh_next = head.hh_head;

可以看出是插入到bucket链表的头部的,这与插入到app链表的尾部不同。然后修改头部的指针指向新的节点。

图示:

根据上面的理解不难画出下图。 为了方便看出 app链表和bucket链表,分成了两个部分。实际上是合在一块的。

d5273a0102db78b7fbc5200e83820e6a.png

这个图说明了3种结构体的关系,以及在app链表关系。新的节点插入在app链表的尾部。

736c55de961d36b81af2b5a79b706cc3.png

这个图显示了bucket和它们的关系。假设左边的两个节点求得的hash值是一致的,那么会被分配到同样的bucket里面去。左上的节点先加入,左下的节点后加入。

可以看出节点是加入到bucket链表的头部的。

其他:

在根据key和key_len计算出hash值后,存在hashv处。通过

[cpp]  view plain copy

#define HASH_TO_BKT( hashv, num_bkts, bkt ) \

do{ \

bkt = ((hashv) & ((num_bkts) - 1)); \

} while(0)

来将其分配到bucket中去的。可以看到每个用户定义的项对应于1个bucket,含有相同hashv的项对应同一个bucket,一个bucket链表里面的各个节点其hashv有可能相同也有可能不同。

[cpp]  view plain copy

#define DECLTYPE_ASSIGN(dst,src) \

do{ \

(dst) = DECLTYPE(dst)(src); \

} while(0)

将src强制转化成dst的类型,再把值赋给dst;

[cpp]  view plain copy

/* calculate the element whose hash handle address is hhe */

#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))

经常通过这个求出用户自定义结构体的首地址。因为要满足对用户自定义的类型的兼容性,所以采用了void * 类型。

UT_hash_handle结构体中的prev和next都是void * 类型的,也是这个原因。

根据上面的情况,不难猜出:

HASH_ITER  是通过遍历app链表实现的

HASH_FIND 是通过遍历 bucket链表实现的

其他类似资料:  http://blog.csdn.net/hongqun/article/details/6103275

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值