8.3 KiB
介绍
map映射与set集合很类似,是逻辑上离散的容器,与set不同的是,set的数据存储方式是以index-data的形式存在,而map映射则是以key-value键值对的形式存在,set的index是整型data可以是任意型,而map的key可以任意型value也可以是任意型。
varch的map容器,在底层实现上采用了“红黑树”,与set一致,增删操作的效率高,而且也支持随机访问。map也支持迭代器。
接口
创建和删除map对象
map_t map_create(int dsize, int ksize, void *trans);
void map_delete(map_t map);
#define map(ktype, dtype) // 为了更简便的使用,对map_create套一层宏定义
#define _map(map) // 对map_delete套一层宏定义,并在map删除后置为空
其中map_t为map的结构体,创建方法则会返回一个空的map对象,创建失败则返回NULL,其中dsize传入数据的大小,ksize传入key的大小,trans是key的转换句柄函数。
ksize和trans在map_cfg配置文件中定义,直接使用map(ktype, dtype)更方便。
map默认支持了一些key的类型,包括char、int、float、double、string(字符串),其他类型也可以在cfg文件中进行添加。
删除方法则是删除传入的map对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。
void test(void)
{
map_t map_char = map(char, int);
map_t map_int = map(int, int);
map_t map_float = map(float, int);
map_t map_double = map(double, int);
map_t map_string = map(string, int);
_map(map_char);
_map(map_int);
_map(map_float);
_map(map_double);
_map(map_string);
}
这例子里面,value的类型都定为了int,其他任意类型都可以。
map的插入和移除
#define map_insert(map, key, value)
#define map_erase(map, key)
map有着较好插入和移除的效率,不用数据移位只需修改链表指向。
插入的方法是添加指定key并将数据复制到这个键(在其中,data传入NULL时则只是开辟空间,不进行赋值),在插入key的过程中会进行查重,保证key的唯一性,插入成功后返回插入后的数据的地址,插入失败则是返回NULL。而移除则是移除指定键的数据,成功返回1,失败返回0。
因为保证key是可以传入不同数据结构,所以用了宏定义传入。
map数据的读写
#define map_data(map, key)
#define map_at(map, vtype, key)
void* map_error(map_t map);
map_data方法就是根据键来获取数据的地址,返回的则是指定的数据的地址,map_error()则是失败。而map_at则是在map_data的基础上加多类型,map_data具备读写保护机制,因为返回的是map_error()而不是NULL,所以在使用map_at方法key写错了就会修改map_error()指向的内容,而不会导致奔溃。
void test(void)
{
int value;
map_t map = map(string, int);
if (!map) return;
value = 100; map_insert(map, "hello", &value);
value = 110; map_insert(map, "Zhang", &value);
printf("map[hello] = %d\r\n", map_at(map, int, "hello"));
printf("map[Zhang] = %d\r\n", map_at(map, int, "Zhang"));
map_erase(map, "hello");
_map(map);
}
结果:
map[hello] = 100
map[Zhang] = 110
map的大小和和数据大小
int map_size(map_t map);
int map_ksize(map_t map);
int map_vsize(map_t map);
map的size很好理解,也就是像数组那样的大小,ksize也就是创建时候传入的key的大小(string类型的key,因为是指针,不是实体,ksize为0),vsize也就是值得大小。
void test(void)
{
int value;
map_t map = map(string, int);
if (!map) return;
value = 100; map_insert(map, "hello", &value);
value = 110; map_insert(map, "Zhang", &value);
printf("size = %d, key size = %d, value size = %d\r\n", map_size(map), map_ksize(map), map_vsize(map));
_map(map);
}
结果:
size = 2, key size = 0, value size = 4
map查找
#define map_find(map, key)
这个方法其实套map_data实现,只是find成功返回1失败返回0。
map迭代器
void map_it_init(map_t map, int orgin);
void* map_it_get(map_t map, void **kaddress, int *ksize);
map也支持内置的迭代器,但主要map的迭代器用于遍历。map是离散型的key,无法通过这种逐一递增的方式进行遍历,所以这里给定了两个迭代器函数用于遍历map。
map_it_init初始化迭代器,orgin指定为MAP_HEAD或者MAP_TAIL,就分别是正向迭代和反向迭代。
map_it_get获取迭代,更新迭代位置,**kaddress为输出的key(当前所在的key,也可以传入NULL不接收),*ksize为输出的key的大小(当前所在的key,也可以传入NULL不接收),返回迭代位置的数据。
通过map_size来把控迭代次数。
void test(void)
{
map_t map = map(string, int);
int value;
char *key;
void *data;
int i;
value = 100; map_insert(map, "hello", &value);
value = 1; map_insert(map, "ZhangSan", &value);
value = 2; map_insert(map, "LiSi", &value);
value = 3; map_insert(map, "WangWu", &value);
value = 4; map_insert(map, "SunLiu", &value);
value = 5; map_insert(map, "QianQi", &value);
map_it_init(map, MAP_HEAD);
i = map_size(map);
while (i--)
{
data = map_it_get(map, &key, NULL);
printf("map[%s] = %d\r\n", key, *(int *)data);
}
_map(map);
}
结果:
map[LiSi] = 2
map[QianQi] = 5
map[SunLiu] = 4
map[WangWu] = 3
map[ZhangSan] = 1
map[hello] = 100
源码解析
在红黑树存储部分和set是一致的,不同之处是支持了多种的key的类型。
比如在原来set传入index的地方,都换成如下的key类型
typedef struct
{
void* address;
int size;
} MKEY;
而在红黑树二叉查找,由比较index的大小,换成了
static int key_compare(MKEY key0, MKEY key1)
{
unsigned char *add0 = (unsigned char *)key0.address;
unsigned char *add1 = (unsigned char *)key1.address;
while (key0.size && key1.size)
{
key0.size--;
key1.size--;
if (*add0 < *add1) return -1;
else if (*add0 > *add1) return 1;
add0++;
add1++;
}
if (key0.size < key1.size) return -1;
else if (key0.size > key1.size) return 1;
return 0;
}
这个函数会实际去比较key的存储空间里面的存储内容,一个byte一个byte的比较。
那怎么实现把形参的key转成key的地址和大小呢?
这个就是前面map_create方法中的trans参数了,这个就是将形参的key转换为key地址和大小的转换函数句柄,都是定义在map_cfg.c文件中的函数,所以要添加支持的key类型,就在map_cfg文件中添加。
来看查找方法的一个具体流程。
#define map_find(map, key) map_find((map), (key))
int map_find(map_t map, ...);
这里map_find第二个参数定义为不定参数,就是为了支持传入不同的数据类型,比如传入int、float、string,而在承接参数时候,就在trans函数中针对不同类型进行承接。
int map_find(map_t map, ...)
{
va_list args;
MKEY key;
if (!map) return 0;
va_start(args, map);
key = *(MKEY *)(map->trans(map, args));
va_end(args);
return map_find_node(map, key)==map->nil?0:1;
}
而map_find函数内部,就不不定参数传给trans函数,就可以返回相应的key了。
剩下的,就交由key_compare函数去比较二分查找。
添加key支持的类型
- 1、 在
map_cfg.c文件中添加trans函数。命名定义void* map_key_trans__xxx(void* map, va_list args)固定格式,xxx就是要的命名类型。 - 2、
trans函数内部实现
int key; // 根据类型定义key
key = va_arg(args, int); // key接收相应类型的不定参数
return map_trans_key(map, &key, sizeof(int)); // 统一返回key的地址和长度,字符串返回字符串长
- 3、 在
map_cfg.h文件中声明void* map_key_trans__xxx(void* map, va_list args); - 4、 在
map_cfg.h文件中定义key的type,xxx同上为key的类型,后面可以定义为MAP_KEY_TYPE_ENTITY或者MAP_KEY_TYPE_POINTER,像字符串这种本身就是指针的,就定义为指针,int就定义为实体
#define MAP_KEY_TYPE__xxx MAP_KEY_TYPE_ENTITY // MAP_KEY_TYPE_POINTER //
- 5、 添加完就可以使用
map(ktype, vtype)来创建map了