## 介绍 map映射与set集合很类似,是逻辑上离散的容器,与set不同的是,set的数据存储方式是以`index-data`的形式存在,而map映射则是以`key-value`键值对的形式存在,set的index是整型data可以是任意型,而map的key可以任意型value也可以是任意型。 varch的map容器,在底层实现上采用了“红黑树”,与set一致,增删操作的效率高,而且也支持随机访问。map也支持迭代器。 ## 接口 ### 创建和删除map对象 ```c 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对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。 ```c 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的插入和移除 ```c #define map_insert(map, key, value) #define map_erase(map, key) ``` map有着较好插入和移除的效率,不用数据移位只需修改链表指向。 插入的方法是添加指定key并将数据复制到这个键(在其中,data传入NULL时则只是开辟空间,不进行赋值),在插入key的过程中会进行查重,保证key的唯一性,插入成功后返回插入后的数据的地址,插入失败则是返回NULL。而移除则是移除指定键的数据,成功返回1,失败返回0。 因为保证key是可以传入不同数据结构,所以用了宏定义传入。 ### map数据的读写 ```c #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()`指向的内容,而不会导致奔溃。 ```c 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的大小和和数据大小 ```c 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`也就是值得大小。 ```c 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查找 ```c #define map_find(map, key) ``` 这个方法其实套`map_data`实现,只是find成功返回1失败返回0。 ### map迭代器 ```c 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`来把控迭代次数。 ```c 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类型 ```c typedef struct { void* address; int size; } MKEY; ``` 而在红黑树二叉查找,由比较index的大小,换成了 ```c 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`文件中添加。 来看查找方法的一个具体流程。 ```c #define map_find(map, key) map_find((map), (key)) int map_find(map_t map, ...); ``` 这里`map_find`第二个参数定义为不定参数,就是为了支持传入不同的数据类型,比如传入`int`、`float`、`string`,而在承接参数时候,就在`trans`函数中针对不同类型进行承接。 ```c 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`函数内部实现 ```c 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就定义为实体 ```c #define MAP_KEY_TYPE__xxx MAP_KEY_TYPE_ENTITY // MAP_KEY_TYPE_POINTER // ``` * 5、 添加完就可以使用`map(ktype, vtype)`来创建map了