varch/doc/varch:map容器.md
2024-04-22 00:09:51 +08:00

8.3 KiB
Raw Blame History

介绍

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的转换句柄函数。 ksizetrans在map_cfg配置文件中定义直接使用map(ktype, dtype)更方便。
map默认支持了一些key的类型包括charintfloatdoublestring字符串,其他类型也可以在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为0vsize也就是值得大小。

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第二个参数定义为不定参数,就是为了支持传入不同的数据类型,比如传入intfloatstring,而在承接参数时候,就在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的typexxx同上为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了