redis 压缩链表

概述

压缩链表是相对于普通链表而言的

当普通链表的数据越来越多, 链表查询性能会低效

当存储的数据较少时, 使用链表存储会浪费空间

压缩链表本质上是一个字符串

压缩链表内存储的数据只能是 整型, 字符串

压缩链表结构

<zlbytes> <zltail> <zllen> <entry1> <entry2> .. <entryN> <zlend>
属性 用途 说明 长度 备注
zlbytes 压缩链表占用的内存总字节数 可以直接对内存大小进行调整, 无需遍历整个链表获取大小 32 位 #define ZIPLIST_BYTES(zl) (*((uint32_t *)(zl)))
zltail 压缩链表尾节点的偏移量 长度 1 字节, 0xFF; pop 操作无需遍历整个链表 32 位 #define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t *)((zl)+sizeof(uint32_t))))
zllen 压缩链表存储的节点总数 zllen 大于 2^16 - 2 时, 需要遍历整个链表来获取长度 16 位 #define ZIPLIST_LENGTH(zl) (*((uint16_t *)((zl)+sizeof(uint32_t) * 2)))
entryN 节点
zlend 压缩链表结束符 0xFF 8 位 #define ZIP_END 255
  • entryN

    • header 头部

      • 前置节点编码规则 (前置节点的长度, 从后向前遍历时用)

        如果前置节点长度小于 ZIP_BEGIN 字节(254), 则使用 1 个字节表示长度
        如果前置节点大于等于 ZIP_BEGIN 字节(254), 则使用 5 个字节表示长度
        a) 第 1 个字节为 ZIP_BEGIN, 表示这是一个 5 字节长度
        b) 之后的 4 个字节表示长度
      • 当前节点编码规则 (当前节点的长度)

        整数

        前 2 位为 1

        编码长度为 1 字节

        表示 说明 备注
        1100 0000 表示 2 字节长度 0xc0 位或 0<<4
        1101 0000 表示 4 字节长度 0xc0 位或 1<<4
        1110 0000 表示 8 字节长度 0xc0 位或 2<<4
        1111 0000 表示 3 字节长度 0xc0 位或 3<<4
        1111 1110 表示 1 字节长度 0xfe
        1111 xxxx (除去 0000, 1111) 表示 (xxxx - 1) 位长度
        1111 1111 压缩链表结束符

        字符串

        前 2 位不是 11

        前 2 位用来表示字符串所占用的字节

        编码长度为 1字节, 2字节, 5字节

        表示 说明 备注
        00pp pppp 1字节, 表示字符串长度小于等于 2^6 -1 前 2 位不用来表示长度
        01pp pppp qqqq qqqq 2字节, 表示字符串长度小于等于 2^14 - 1 前 2 位不用来表示长度
        10pp pppp qqqq qqqq ... 5字节, 表示字符串长度大于等于 2^14 前 2 位不用来表示长度
    • body 数据部分

      // ziplist 节点信息的结构
      typedef struct zlentry {
      // prevrawlen :前置节点的长度
      // prevrawlensize :编码 prevrawlen 所需的字节大小
      unsigned int prevrawlensize, prevrawlen;
      // len :当前节点值的长度
      // lensize :编码 len 所需的字节大小
      unsigned int lensize, len;
      // 当前节点 header 的大小
      // 等于 prevrawlensize + lensize
      unsigned int headersize;
      // 当前节点值所使用的编码类型
      unsigned char encoding;
      // 指向当前节点的指针
      unsigned char *p;
      } zlentry;
      注意
      ziplist 对于长度的存储编码, 当要使用长度的时候对长度进行解码, 放于 entry 实体的 prevrawlen 与 len 均为实际的长度值, prevrawlensize 与 lensize 为对实际长度进行编码所占的字节数

ziplist 宏定义 (src/ziplist.c)

宏名称 作用 备注
ZIP_END 压缩链表尾部标识 #define ZIP_END 255
ZIP_BEGIN 前置节点使用 5 字节表示长度的标识符 #define ZIP_BIGLEN 254
ZIP_STR_MASK 字符串类型的掩码 #define ZIP_STR_MASK 0xc0
ZIP_INT_MASK 整数类型的掩码 #define ZIP_INT_MASK 0x30
ZIP_STR_06B 字符串编码类型 #define ZIP_STR_06B (0 << 6)
ZIP_STR_14B 字符串编码类型 #define ZIP_STR_14B (1 << 6)
ZIP_STR_32B 字符串编码类型 #define ZIP_STR_32B (2 << 6)
ZIP_INT_16B 整数编码类型 (2字节) #define ZIP_INT_16B (0xc0 位或 0<<4)
ZIP_INT_32B 整数编码类型 (4字节) #define ZIP_INT_32B (0xc0 位或 1<<4)
ZIP_INT_64B 整数编码类型 (8字节) #define ZIP_INT_64B (0xc0 位或 2<<4)
ZIP_INT_24B 整数编码类型 (3字节) #define ZIP_INT_24B (0xc0 位或 3<<4)
ZIP_INT_8B 整数编码类型 (1字节) #define ZIP_INT_8B 0xfe
ZIP_INI_IMM_MASK 对于 1111 xxxx 后4位表示编码类型的整数掩码 #define ZIP_INT_IMM_MASK 0x0f
ZIP_INI_IMM_MIN 4位整数编码的最小值 #define ZIP_INT_IMM_MIN 0xf1
ZIP_INI_IMM_MAX 4位整数编码的最大值 #define ZIP_INT_IMM_MAX 0xfd
ZIP_INI_IMM_VAL 计算4位整数编码 #define ZIP_INT_IMM_VAL(v) (v & ZIP_INT_IMM_MASK)
INT24_MAX 24位整数编码的最大值 #define INT24_MAX 0x7fffff
INT24_MIN 24位整数编码的最小值 #define INT24_MIN (-INT24_MAX - 1)
ZIP_IS_STR 查看指定编码是否是字符串编码 #define ZIP_IS_STR(enc) (((enc) & ZIP_STR_MASK) < ZIP_STR_MASK)
ZIPLIST_BYTES 获取压缩链表的 zlbytes 值 #define ZIPLIST_BYTES(zl) (*((uint32_t *)(zl)))
ZIPLIST_TAIL_OFFSET 获取压缩链表的 zltail 值 #define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t *)((zl)+sizeof(uint32_t))))
ZIPLIST_LENGTH 获取压缩链表的 zllen 值 #define ZIPLIST_LENGTH(zl) (*((uint16_t *)((zl)+sizeof(uint32_t) * 2)))
ZIPLIST_HEADER_SIZE 压缩链表表头的大小 #define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t))
ZIPLIST_ENTRY_HEAD 压缩链表头节点指针 #define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE)
ZIPLIST_ENTRY_TAIL 压缩链表尾节点指针 #define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
ZIPLIST_ENTRY_END 压缩链表末端 zlend 的指针 #define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)
ZIPLIST_INCR_LENGTH 增加压缩链表的 zlen 的值, 增加幅度为 incr #define ZIPLIST_INCR_LENGTH(zl,incr)
ZIP_ENTRY_ENCODING 从 ptr 中获取编码类型, 并将其存入 encoding #define ZIP_ENTRY_ENCODING(ptr, encoding)
ZIP_DECODE_LENGTH 从 ptr 中获取 encoding, lensize, len #define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len)
ZIP_DECODE_PREVLENSIZE 解码 ptr, 设置 prevlensize 的值 #define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize)
ZIP_DECODE_PREVLEN 解码 ptr, 设置 prevlensize, prevlen #define ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen)

ziplist api (src/ziplist.c)

函数 作用 备注
zipIntSize 保存 encoding 所需的字节数 (仅针对整数编码类型) static unsigned int zipIntSize(unsigned char encoding)
zipEncodeLength 将长度 rawlen 按照 encoding 进行编码, 并存到指针 p 中 static unsigned int zipEncodeLength(unsigned char *p, unsigned char encoding, unsigned int rawlen)
zipPrevEncodeLength 编码前置节点 len, 存入节点指针 p static unsigned int zipPrevEncodeLength(unsigned char *p, unsigned int len)
zipPrevEncodeLengthForceLarge 编码前置节点 len 用 5 个字节存储, 存入节点指针 p static void zipPrevEncodeLengthForceLarge(unsigned char *p, unsigned int len)
zipPrevLenByteDiff 计算编码 len 所需的字节数, 与原 p 中存储的 prevlensize 进行减法 static int zipPrevLenByteDiff(unsigned char *p, unsigned int len)
zipRawEntryLength 返回节点所占用的字节数 static unsigned int zipRawEntryLength(unsigned char *p)
zipTryEncoding 尝试将字符串转换为整数, 若转换成功, 存储到 v, 并将编码方式存储到 encoding static int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding)
zipSaveInteger 以 encoding 指定的编码方式, 将整数值 value 写入到 p static void zipSaveInteger(unsigned char *p, int64_t value, unsigned char encoding)
zipLoadInteger 以 encoding 指定的编码方式, 读取并返回 p 中的整数值 static int64_t zipLoadInteger(unsigned char *p, unsigned char encoding)
zipEntry 获取指定指针 p 的节点 static zlentry zipEntry(unsigned char *p)
ziplistNew 创建并返回一个新的 ziplist unsigned char *ziplistNew(void)
ziplistResize 调整 ziplist 到指定的 len 长度 static unsigned char *ziplistResize(unsigned char *zl, unsigned int len)
__ziplistCascadeUpdate 当一个节点添加到某个节点之前时, 有可能相应节点的 header 空间不足够, 为了处理由此产生的迭代更新; 对于长度变小的收缩情况不做考虑 static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p)
__ziplistDelete 在 zl 中, 从指定位置 p 开始, 连续删除 num 个节点 static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num)
__ziplistInsert 将字符串 s 插入到 zl 的指定位置 p static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen)
ziplistPush 向 zl 头部或尾部压入字符串 s unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where)
ziplistIndex 根据给定的 index, 获取 zl 节点 unsigned char *ziplistIndex(unsigned char *zl, int index)
ziplistNext 返回给定节点 p 的下个节点 unsigned char *ziplistNext(unsigned char *zl, unsigned char *p)
ziplistPrev 返回给定节点 p 的前置节点 unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p)
ziplistGet 获取指定节点 p 的值, 若是字符串, 字符串指针存储在 *sstr, 字符串长度存储在 *slen; 若是整数, 将值存储在 *sval unsigned int ziplistGet(unsigned char *p, unsigned char **sstr, unsigned int *slen, long long *sval)
ziplistInsert 将字符串 s 插入到 zl 的指定位置 p unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen)
ziplistDelete 删除 zl 中指定位置 p 的节点, 并更新 p unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p)
ziplistDeleteRange 删除 zl 中从 index 开始, 连续 num 个节点 unsigned char *ziplistDeleteRange(unsigned char *zl, unsigned int index, unsigned int num)
ziplistCompare 将 p 所指向的节点值与给定的字符串 sstr 进行比较, 相等返回1, 不相等返回0 unsigned int ziplistCompare(unsigned char *p, unsigned char *sstr, unsigned int slen)
ziplistFind 从指定节点 p 开始, 每次跳过 skip 个节点, 将节点值与字符串 vstr 比较, 找到相等的节点, 返回 unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip)
ziplistLen 返回 zl 的节点数, 若节点数小于 2^16 - 2 时, 直接取 zllen, 若大于此值, 则进行遍历计算长度 unsigned int ziplistLen(unsigned char *zl)
ziplistBlobLen 返回 zl 占用的字节总数 zlbytes size_t ziplistBlobLen(unsigned char *zl)
ziplistRepr 打印 zl 的一些信息 (zlbytes, zllen, zltail, entryN) void ziplistRepr(unsigned char *zl)

redis 压缩链表的更多相关文章

  1. Redis压缩列表原理与应用分析

    摘要 Redis是一款著名的key-value内存数据库软件,同时也是一款卓越的数据结构服务软件.它支持字符串.列表.哈希表.集合.有序集合五种数据结构类型,同时每种数据结构类型针对不同的应用场景又支 ...

  2. Redis数据结构—链表与字典的结构

    目录 Redis数据结构-链表与字典的结构 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 Redis字典的使用 Re ...

  3. Redis数据结构—链表与字典

    目录 Redis数据结构-链表与字典 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 哈希算法 解决键冲突 rehas ...

  4. Redis压缩列表

    此篇文章是主要介绍Redis在数据存储方面的其中一种方式,压缩列表.本文会介绍1. 压缩列表(ziplist)的使用场景 2.如何达到节约内存的效果?3.压缩列表的存储格式 4. 连锁更新的问题  5 ...

  5. Redis学习——链表源码分析

    0. 前言 Redis 中的链表是以通用链表的形式实现的,而对于链表的用途来说,主要的功能就是增删改查,所以对于查找来说,redis其提供了一个match函数指针,用户负责实现其具体的匹配操作,从而实 ...

  6. redis link 链表结构

    lpush key value 作用: 把值插入到链接头部 rpop key 作用: 返回并删除链表尾元素 lrange key start stop 作用: 返回链表中[start ,stop]中的 ...

  7. Redis笔记 -- 链表和链表节点的API函数(三)

    链表和链表节点API 函数 作用 时间复杂度 listSetDupMethod 将给定的函数设置为链表的节点值复制函数 复制函数可以通过链表的dup属性直接获得,O(1) listGetDupMeth ...

  8. Redis 压缩存储的配置

    如题,redis是采用了ziplist 元素在不足一定数量时采用压缩存储 hash: zset: list: 如上图所示: ziplist-entries:最大元素数量(即存储了多少个元素) zipl ...

  9. redis 笔记01 简单动态字符串、链表、字典、跳跃表、整数集合、压缩列表

    文中内容摘自<redis设计与实现> 简单动态字符串 1. Redis只会使用C字符串作为字面量,在大多数情况下,Redis使用SDS(Simple Dynamic String,简单动态 ...

随机推荐

  1. Vue 2.0初学后个人总结及分享

    摘要:最近在上海找工作,发现Vue前景还不错,于是就打算先学习一下(之前了解过,但是一直没提到日程上)这篇随笔当是为了自己学习之后,做一个小的阶段性总结.希望本文的内容对于刚开始接触vue的朋友们有点 ...

  2. Delete Node in a Linked List leetcode

    Write a function to delete a node (except the tail) in a singly linked list, given only access to th ...

  3. nodejs学习第一天之模块

    1.运行js文件 2.node 与 js 的区别 相同:数据类型,语法结构,对象  等基本一致 不同:在js中的顶层对象window 在node中没有在node中 顶层对象为global对象 其不对外 ...

  4. 关于vue-clidown到本地后,拷贝文件库到另外一台电脑上npm run dev编译报错的处理

    这些天自己在用vue-cli项目,在家里的电脑下下来后写了一些demo,拿到公司继续开发的时候发现删除node_modules文件,运行npm install和npm run 百度,搜狗了好久都没有找 ...

  5. 用C写一个web服务器(一) 基础功能

    .container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .conta ...

  6. vector 对象中存放指针类型数据

    <<C++ Primer>> 第四版Exercise Section 5.6 的5.1.6 有一道题是这样的:编写程序定义一个vector对象,其每个元素都是指向string类 ...

  7. iOS PureLayout使用

    PureLayout是iOS Auto Layout的终端API,强大而简单.由UIView.NSArray和NSLayoutConstraint类别组成.PureLayout为大多数Auto Lay ...

  8. HTTP协议&SOCKET协议

    一. HTTP协议是什么? 我们在浏览器的地址栏里输入的网站地址叫做 URL(UniformResourceLocator,统一资源定位符).就像每家每户都有一个门牌地址一样,每个网页也都有一个Int ...

  9. 关于npm安装全局模块,require时报Error: Cannot find module 'XXX'的解决办法

    系统环境:centos 下午使用npm安装"cheerio",想搞爬虫玩玩. npm安装有两种模式: 本地 # npm install cheerio 全局 # npm insta ...

  10. linux下大于2T的硬盘格式化方法

    我们先在超级用户模式下用fdisk -l命令查看挂载的硬盘设备,假设设备号为/dev/sdb,接下来我们使用parted命令来进行GPT分区:1. yum install parted -y# par ...