一、引言

  上一节我们总结了跳跃表的知识,我们知道了有序数组可以用跳跃表实现,也可以用压缩列表来实现,这一篇文章我们来总结一下压缩列表相关的知识。

二、压缩列表简介

  定义:压缩列表 ziplist 本质上是一个字节数组,每个元素可以是一个字节数组一个整数

  PS:Redis的有序集合散列列表都直接或间接用到了压缩列表。

三、压缩列表的存储结构

  我们通过一张图来直观地分析压缩列表的结构:

  • zlbytes :压缩列表的字节长度,占4个字节,因此压缩列表最多有232-1个字节 。
  • zltail :压缩列表尾元素相对于压缩列表起始地址的偏移量,占4个字节。
  • zllen :压缩列表的元素个数,占2个字节。zllen无法储存元素个数超过65535(216-1)的压缩列表。
  • entryX:压缩列表储存的元素,可以是字节数组或者整数,长度不限。
  • zlend:压缩列表的结尾,占1个字节,恒为0xFF

  PS:1字节=8位,二进制里,每位对应2的n次方,1字节最大值为28-1个元素。

  PS:必须遍历整个压缩列表才能获取到元素个数。

四、压缩列表的元素的结构

  还是通过一张图来说明就比较直观一些:

  • previous_entry_length :表示前一个元素的字节长度占用1个字节或者5个字节,根据前一个元素的长度大小来定

    • 小于254字节 :用1个字节表示previous_entry_length
    • 大于或等于254字节 :用5个字节表示previous_entry_length,此时previous_entry_length的第一个字节为固定的0xFE,后面4个字节才真正表示前一个元素的长度。
  • encoding :表示当前元素的编码,即content字段储存的数据类型(整数或者字节数组)占用1、2或5个字节,根据content类型来定【详见压缩列表的元素编码表】
  • content :储存数据内容长度不限

  PS:假设已知当前元素的首地址p,就可以根据 p-previous_entry_length 推算出前一个元素的首地址。

压缩列表的元素编码

  简单总结一下:

  • 前两位 :判断类型(整数 OR 字节数组)PS:若为字节数组还可根据前两位判断最大长度
  • 字节数组时 :后面的位数标识字节数组的实际长度(2n-1)
  • 整数时 :3、4位判断类型,后4为用于表示整数值

五、结构体

  意义:由于每次获取前一个元素的内容都需要经过复杂的解码运算,影响效率,所以定义结构体 zlentry 用于表示解码后的压缩列表元素。

  PS:结构体实际上是对每个元素的首部【 previous_entry_length 和 encoding 】结构的细分描述。

结构体的字段

  • prevrawlensize :previous_entry_length 字段的长度
  • prevrawlen :previous_entry_length 字段的内容
  • lensize :encoding 字段的长度
  • encoding :表示数据类型
  • len :数据内容的长度
  • p :当前元素的首地址
  • headersize :当前元素的首部长度previous_entry_length + encoding 】

解码步骤

  解码主要用到的是 zipEntry 方法,解码出来的信息储存于 zlentry 结构体:

  • 1、解码元素的 previous_entry_length 字段
  • 2、解码元素的 encoding 字段
  • 3、设置好结构体的相关字段

六、创建压缩列表

  创建压缩列表主要用到的方法为ziplistNew【返回参数为压缩列表的首地址】

  创建一个空的压缩列表需要分配的初始空间为11(4+4+2+1)个字节

  • 4 :zlbytes【压缩列表的字节长度:4字节】
  • 4 :zltail【尾元素对起始地址的偏移量:4字节】
  • 2 :zlbytes【压缩列表的元素个数:2字节】
  • 1 :zlbytes【压缩列表的结尾,固定0xFF:1字节】

七、插入元素

  插入元素主要用到的方法为ziplistInsert【返回参数为压缩列表的首地址】

  步骤:

  • 将元素内容编码
  • 重新分配空间
  • 复制数据

将元素内容编码

  含义:将元素内容编码就是计算 previous_entry_length 字段、 encoding 字段、 content 字段的内容。

  计算前一个元素的长度又根据插入位置分为三种情况:

  • P0【插入第一个元素】 :即不存在前一个元素,则 previous_entry_length 为0。
  • P1【插入列表中间】 :则entryX+1储存的 previous_entry_length 就是entryX元素的长度,也就是我们需要的长度。
  • P1【插入列表末尾】 :则需要进行解码操作,参考上面的解码步骤

重新分配空间

  首先定义几个变量:

  • curlen :插入元素前列表的长度
  • reqlen :新插入元素的长度
  • nextdiff :entryX+1元素长度的变化
  • forcelarge :标识当nextdiff=4而reqlen<4的情况

  PS:需要考虑连锁更新的情况~

  详见书本解释~

数据复制

  含义:插入元素以后,插入点之后的元素都要向后进行移动,此时底层实现为复制元素到新的内存地址上。

  几个名词:

  • 偏移量 :待插入元素entryNew的长度
  • 移动的数据块长度 :插入位置P后所有元素长度之和 + nextdiff

  PS:数据移动之后还需要更新entryX+1元素的 previous_entry_length 字段

八、删除元素

  删除元素主要用到的方法为ziplistDelete【返回参数为压缩列表的首地址】

  步骤:

  • 计算待删除元素的总长度
  • 复制数据
  • 重新分配空间

  PS:新增元素是从新分配空间小于指针指向的空间时,可能会出现问题;而删除元素时,内存空间是肯定减小的,但是由于是先复制数据再发分配空间,则多余空间的数据已经复制好了,所以从新分配空间就不会产生这个问题。

九、遍历压缩列表

  向前遍历:

  • 通过 previous_entry_length 字段可以计算前一个元素的首地址,则相对容易遍历

  向后遍历:

  • 解码当前元素
  • 计算当前元素长度
  • 获取到最后一个元素的首地址

十、连锁更新

  连锁更新其实很好理解,就是更新一个元素的同时引发其他元素的同时更新,这种现象就叫连锁更新:

我们可以看到,当在以下两种情况:

  • P1位置删除entryX元素
  • P2位置插入entryY元素

  都会导致连锁更新的发生,为什么呢?

P1位置删除entryX元素

   删除前,entryX的长度是128字节,所以entryX+1的 previous_entry_length 字段只需要1个字节就可以储存。

  但是当删除了entryX元素时,entryX+1的前一个元素变成了entryX-1,而entryX-1的长度为512字节,所以此时entryX+1的 previous_entry_length 字段需要用5个字节来储存

   entryX+1的 previous_entry_length 字段从1个字节变成5个字节以后,entryX+1的长度变成257字节,同时引发entryX+2也需要去改变它的 previous_entry_length 字段长度,从而引发连锁更新...

P2位置插入entryY元素

  与上面类似,就不再过多描述了。

Redis5设计与源码分析读后感(四)压缩列表的更多相关文章

  1. Redis5设计与源码分析读后感(一)认识Redis

    一.初识redis 定义 Redis是一个开源的Key-Value数据库,通常被称为数据结构服务器,其值可以是多种常见的数据格式,且读写性能极高,且所有操作都是原子性的. 高性能的主要原因 1.基于内 ...

  2. Redis5设计与源码分析读后感(三)跳跃表

    一.引言 有序集合在日常开发中相当常见,比如做排名等相关的功能,肯定要用到排序的功能,那么常见底层实现有很多种: 数组 :不便于元素的插入和删除 链表 :查询效率低,需要遍历所有元素 平衡树OR红黑树 ...

  3. Redis5设计与源码分析读后感(二)简单动态字符串SDS

    一.引言 学习之前先了解几个概念: SDS定义:简单动态字符串,Redis的基本数据结构之一,用于储存字符串和整型数据. 二进制安全:C语言中用"\0"表示字符串结束,如果字符串本 ...

  4. ABP源码分析十四:Entity的设计

    IEntity<TPrimaryKey>: 封装了PrimaryKey:Id,这是一个泛型类型 IEntity: 封装了PrimaryKey:Id,这是一个int类型 Entity< ...

  5. Docker源码分析(四):Docker Daemon之NewDaemon实现

    1. 前言 Docker的生态系统日趋完善,开发者群体也在日趋庞大,这让业界对Docker持续抱有极其乐观的态度.如今,对于广大开发者而言,使用Docker这项技术已然不是门槛,享受Docker带来的 ...

  6. Heritrix源码分析(十四) 如何让Heritrix不间断的抓取(转)

    欢迎加入Heritrix群(QQ):109148319,10447185 , Lucene/Solr群(QQ) :  118972724 本博客已迁移到本人独立博客: http://www.yun5u ...

  7. Heritrix源码分析(十四)

    近段时间在搞定Lucene的一些问题,所以Heritrix源码分析暂时告一段落.今天下午在群里有同学提到了Heritrix异常终止的问题以及让Heritrix不停的抓取(就是抓完一遍后载入种子继续抓取 ...

  8. Java集合源码分析(四)HashMap

    一.HashMap简介 1.1.HashMap概述 HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射.此类不保证映射的顺序,假定哈希函数将元素 ...

  9. 插件开发之360 DroidPlugin源码分析(四)Activity预注册占坑

    请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52258434 在了解系统的activity,service,broa ...

随机推荐

  1. 什么情况下适合用UDP协议,什么情况下适合用TCP协议?

    总的来说 TCP协议提供可靠的服务, UDP协议提供高效率的服务. 高可靠性的TCP服务提供面向连接的服务,主要用于一次传输大量报文的情形, 如文件传输,远程登录等: 高效率的UDP协议提供无连接的数 ...

  2. 安国AU6989主控 + K9GBG08U0A(NAND) 制作4GB闪存驱动器

    文档标识符:AU6989_FLASH-DRIVE_D-P8 作者:DLHC 最后修改日期:2020.8.22 本文链接: https://www.cnblogs.com/DLHC-TECH/p/AU6 ...

  3. Vue源码分析之实现一个简易版的Vue

    目标 参考 https://cn.vuejs.org/v2/guide/reactivity.html 使用 Typescript 编写简易版的 vue 实现数据的响应式和基本的视图渲染,以及双向绑定 ...

  4. Intriguing properties of neural networks

    郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! https://arxiv.org/abs/1312.6199v4 Abstract 深度神经网络是近年来在语音和视觉识别任务中取得最新性 ...

  5. 牛客网PAT练兵场-统计同成绩学生

    题解:开100的数组,进行存储人数,方便查询 题目地址:https://www.nowcoder.com/questionTerminal/3df4810cc0664b8bb848d785f68f7c ...

  6. Angular 学习思路

    近些年前端框架非常多,主流的有 Vue.React.Angular 等.我参与的项目中使用较多的是 Vue.因为 Vue 学习难度不大,上手很快,代码简洁,而且使用 Vue 全家桶(Vue + Vue ...

  7. 方法在class文件中的存在形式MethodInfo

    一个方法对由一个method_info结构所定义.一个class文件中,不会同时出现两个方法同时有相同的名称和描述符 method_info结构如下 参考Field 代码实现 public class ...

  8. 单元测试与单元测试框架 Jest

    什么是单元测试? 测试是一种验证我们的代码是否可以按预期工作的手段. 被测试的对象可以是我们程序的任何一个组成部分.大到一个分为多步骤的下单流程,小到代码中的一个函数. 单元测试特指被测试对象为程序中 ...

  9. 洛谷P1080 国王游戏 python解法 - 高精 贪心 排序

    洛谷的题目实在是裹脚布 还编的像童话 这题要 "使得获得奖赏最多的大臣,所获奖赏尽可能的少." 看了半天都觉得不像人话 总算理解后 简单说题目的意思就是 根据既定的运算规则 如何排 ...

  10. Labview学习之路(三)前面板数值控件

    首先看一下前面板都有什么数值控件(我用的labview是17年的,其他版本可能会有不同) 我个人将他们分成了六个部分 第一部分 这个部分大家很好理解,数值输入数值输出,时间输入和时间输出,这里我们讲一 ...