mirror of
https://gitee.com/Lamdonn/varch.git
synced 2025-12-06 16:56:42 +08:00
233 lines
8.3 KiB
Markdown
233 lines
8.3 KiB
Markdown
## 介绍
|
||
|
||
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了
|