03 关于 zipmap
Posted 蓝风9
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了03 关于 zipmap相关的知识,希望对你有一定的参考价值。
前言
关于 redis 的数据结构 zipmap
相关介绍主要围绕着如下测试用例, 来看看 zipmap 的存储, 以及 相关的 api
本文的 zipmap 相关代码 拷贝自 redis-6.2.0
代码来自于 https://redis.io/
这个数据结构在 redis 中已经没有使用了, 呵呵
但是在几年前 我看过的一个的一个版本里面, 它应该是作为了 hash 的底层支撑之一吧
现在看了一下 hash 的实现, 似乎是 hashtable / ziplist 来支撑业务了
不过 zipmap 还是可以了解一下的, 相比于 ziplist 这里的设计简单的多
测试用例
//
// Created by Jerry.X.He on 2021-02-21.
//
#include <iostream>
#include "../libs/sds.h"
#include "../libs/zipmap.h"
using namespace std;
// Test25ZipListUsage
int main(int argc, char **argv) {
unsigned char *zm = zipmapNew();
sds firstKey = sdsnew("name");
sds firstValue = sdsnew("jerry");
sds secondKey = sdsnew("age");
sds secondValue = sdsnew("74");
// zipmapSet
int updated = 0;
zm = zipmapSet(zm, (unsigned char *) firstKey, (unsigned int) sdslen(firstKey),
(unsigned char *) firstValue, (unsigned int) sdslen(firstValue), &updated);
zm = zipmapSet(zm, (unsigned char *) secondKey, (unsigned int) sdslen(secondKey),
(unsigned char *) secondValue, (unsigned int) sdslen(secondValue), &updated);
int len = zipmapLen(zm);
// zipmapGet
unsigned char *nameValue = NULL;
unsigned int nameValueLen = 0;
zipmapGet(zm, (unsigned char *) firstKey, (unsigned int) sdslen(firstKey),
&nameValue, &nameValueLen);
// zipmapExists
int existsResult = zipmapExists(zm, (unsigned char *) firstKey, (unsigned int) sdslen(firstKey));
// zipmapNext
unsigned char *entryKey = NULL;
unsigned char *entryValue = NULL;
unsigned int entryKeyLen = 0, entryValueLen = 0;
unsigned char *i = zipmapRewind(zm);
while ((i = zipmapNext(i, &entryKey, &entryKeyLen, &entryValue, &entryValueLen)) != NULL) {
printf("%d bytes key at %s \\n", entryKeyLen, entryKey);
printf("%d bytes value at %s \\n", entryValueLen, entryValue);
}
// zipDelete
int deleted = 0;
zm = zipmapDel(zm, (unsigned char *) firstKey, (unsigned int) sdslen(firstKey), &deleted);
int newLen = zipmapLen(zm);
// zipmapBlobLen
int zmBytes = zipmapBlobLen(zm);
int x = 0;
}
数据结构
zipmap 的内存结构大致如下 <zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"<zmend>
zmlen : 是第一个字节, 用于表示 zipmap 的长度
<len>"foo"<len><free>"bar" : 表示一个 entry(key + value), key 是 len + 数据, value 是 len + free + 数据
zmend 一字节, 0xff 标记 zipmap 结尾标记[类似于 string 约束 '\\0' 为结束符]
zipmapNew
分配了 两个字节, 1个字节表示长度, 另外一个字节为结束标记
zipmapSet
计算保存 key, value 需要的长度, 标记为 len
从整个 zipmap 中查询 key 对应的 entry, 标记为 p
如果 entry 存在, 如果原有 entry 的 oldLen 小于当前 len, 扩容zipmap, 增加 (len-oldLen), 移动 p 后面的数据
如果 entry 不存在, 扩容zipmap 增加 len, 增加 zipmap 的长度
如果 (len - oldLen) > ZIPMAP_VALUE_MAX_FREE[默认为4], 缩容zipmap, 减少(len-oldLen)个字节, 移动 p后面的数据
写入当前 entry 的 keyLen, key, valueLen, freeLen, value
执行了 "zm = zipmapSet(zm, (unsigned char *) firstKey, (unsigned int) sdslen(firstKey), (unsigned char *) firstValue, (unsigned int) sdslen(firstValue), &updated);" 之后
我们来 inspect 一下 zm
zmLen 为 1
接着为第一个 entry 的 key, 长度为 0x04, 接着四个字节为 key 的数据 "name"
接着为第一个 entry 的 value, 长度为 0x05, 空闲字符为 0x00 个, 接着五个字节为 value 的数据 "jerry"
接着为 0x7fb5f850006d 位置上的 0xff 为 zmend zipmap 的结束标记
(lldb) x 0x7fb5f8500060
0x7fb5f8500060: 01 04 6e 61 6d 65 05 00 6a 65 72 72 79 ff 00 50 ..name..jerry�.P
0x7fb5f8500070: 37 00 85 5f fb 07 00 c0 00 00 00 00 00 00 00 50 7.._�..�.......P
执行了 "zm = zipmapSet(zm, (unsigned char *) secondKey, (unsigned int) sdslen(secondKey), (unsigned char *) secondValue, (unsigned int) sdslen(secondValue), &updated);" 之后
我们来 inspect 一下 zm
zmLen 为 2
接着为第一个 entry 的 key, 长度为 0x04, 接着四个字节为 key 的数据 "name"
接着为第一个 entry 的 value, 长度为 0x05, 空闲字符为 0x00 个, 接着五个字节为 value 的数据 "jerry"
接着为第二个 entry 的 key, 长度为 0x03, 接着四个字节为 key 的数据 "age"
接着为第二个 entry 的 value, 长度为 0x02, 空闲字符为 0x00 个, 接着五个字节为 value 的数据 "74"
接着为 0x7fb5f8500075 位置上的 0xff 为 zmend zipmap 的结束标记
(lldb) x 0x7fb5f8500060
0x7fb5f8500060: 02 04 6e 61 6d 65 05 00 6a 65 72 72 79 03 61 67 ..name..jerry.ag
0x7fb5f8500070: 65 02 00 37 34 ff 00 c0 00 00 00 00 00 00 00 50 e..74�.�.......P
zipmapLen
从 zipmap 的头部获取长度
如果 超过了 254, 则遍历 zipmap 来计算长度
zipmapGet
从整个 zipmap 中查询 key 对应的 entry, 标记为 p
将 value, valueLen 存入 接受指针
zipmapExists
遍历整个 zipmap 中查询 key 对应的 entry, 标记为 p
如果存在, exists
zipmapRewind
跳过 zmlen, 从第一个元素开始迭代
zipmapNext
获取 zm 所在的 entry 的 key, value 的信息
并迭代 zm 到下一个 entry 的位置, 返回
zipmapDel
从整个 zipmap 中查询 key 对应的 entry, 标记为 p
如果不存在, 标记未删除
否则 获取 p 对应的 entry 的长度, 移动 p 之后的节点
缩容 zipmap, 更新 length, 更新删除标记
我们来 inspect 一下 zm
zmLen 为 1
接着为第一个 entry 的 key, 长度为 0x03, 接着四个字节为 key 的数据 "age"
接着为第一个 entry 的 value, 长度为 0x02, 空闲字符为 0x00 个, 接着五个字节为 value 的数据 "74"
接着为 0x7fb5f8500069 位置上的 0xff 为 zmend zipmap 的结束标记
(lldb) x 0x7fb5f8500060
0x7fb5f8500060: 01 03 61 67 65 02 00 37 34 ff 72 72 79 03 61 67 ..age..74�rry.ag
0x7fb5f8500070: 65 02 00 37 34 ff 00 c0 00 00 00 00 00 00 00 50 e..74�.�.......P
zipmapBlobLen
借用了 zipmapLookupRaw 迭代来计算 zipmap 占用的字节数
zipmapRepr
输出 zipmap 的头数据信息, 输出每一个 entry 的相关数据信息
如上面 zm 输出结果如下
为了更深入的理解各个 zipmap 这个数据结构, 初学还是建议直接查看内存信息来剖析
{status 1}{key 3}age{value 2}74{end}
完
以上是关于03 关于 zipmap的主要内容,如果未能解决你的问题,请参考以下文章
条件表达式中的 zipmap 函数给出真假结果表达式必须具有一致的类型错误