这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战
插入流程
上图是 ziplist 初始化的情况:
只有 zlbytes + zltail + zllen 初始化出来,结尾处加一个 ZIP_END 。返回 char *zl ⇒ 连续内存的头指针。
初始化完毕,我们拿着 ziplist 首地址,开始插入新的 entry :
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) {
unsigned char *p;
// p 为插入点,是一个精确的地址值:分头插/尾插
p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);
return __ziplistInsert(zl,p,s,slen);
}
// first_entry_ptr = zl(首地址) + header_size
#define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE)
// last_entry_ptr = zl(首地址) + zltail_ptr(尾部entry偏移量)
#define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
// tail_entry_ptr = zltail_ptr = zl(首地址) + zlbytes_size
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
可以看的出来:__ziplistInsert 是实际插入执行函数,支持插入任意节点位置:
- 当
ziplist中entry为空时,不存在前一个元素,即:pre_entry_length = 0 - 新的 entry 作为最后一个节点插入时,即:p2 → 直接偏移到最后一个 entry
具体可以看下图。
__ziplistInsert
简述一下步骤,不然会迷失在偏移计算当中:
-
将元素内容编码
-
传入的
zl头指针【需要找到最后一个tail_entry的头指针位置,这个和**p 插入点** 不一样】 -
根据传入的位置,计算
pre_entry_length:- ziplist 为空,
pre_entry_length = 0 p1⇒ZIP_DECODE_PREVLEN()直接获取上一个 entry 编码所需的长度p2→ 判断规则:p[0] == ZIP_END⇒ 通过zl头指针找到最后一个 entry ,计算这个tail_entry长度
- ziplist 为空,
-
紧接处理 数据 encoding
-
最终确定当前元素所需空间大小 ⇒
reqlen
-
-
将元素插入 ziplist,重新分配内存空间
Hash
hash 是开发者存储一个对象结构比较理想的数据结构。一个对象的各个属性,正好对应 <field, value> 。
而 hash 底层存储会随着数据量的增大而发生变化:
- field 比较少,value所占空间也比较小时 ⇒ ziplist
- field 增多,value 空间增大 ⇒ dict