一、不同数据类型存储结构

Redis底层数据结构一共有 6 种,分别是简单动态字符串、双向链表、压缩列表、哈希表、跳表和整数数组。它们和数据类型的对应关系如下图所示:

redisson getlist 什么结构 redis底层数据结构_数据结构

1 数组与链表的区别

redisson getlist 什么结构 redis底层数据结构_数据结构_02

什么是ziplist

Redis官方对于ziplist的定义是(出自ziplist.c的文件头部注释):

The ziplist is a specially encoded dually linked list that is designed to be very memory efficient. It stores both strings and integer values, where integers are encoded as actual integers instead of a series of characters. It allows push and pop operations on either side of the list in O(1) time.

翻译一下就是说:ziplist是一个经过特殊编码的双向链表,它的设计目标就是为了提高存储效率。ziplist可以用于存储字符串或整数,其中整数是按真正的二进制表示进行编码的,而不是编码成字符串序列。它能以O(1)的时间复杂度在表的两端提供pushpop操作。

ziplist的数据结构定义

ziplist的数据结构组成是本文要讨论的重点。实际上,ziplist还是稍微有点复杂的,它复杂的地方就在于它的数据结构定义。一旦理解了数据结构,它的一些操作也就比较容易理解了。

我们接下来先从总体上介绍一下ziplist的数据结构定义,然后举一个实际的例子,通过例子来解释ziplist的构成。如果你看懂了这一部分,本文的任务就算完成了一大半了。

从宏观上看,ziplist的内存结构如下:

redisson getlist 什么结构 redis底层数据结构_数组_03

 压缩链表存储结构:

redisson getlist 什么结构 redis底层数据结构_数组_04

各个部分在内存上是前后相邻的,它们分别的含义如下:

  • <zlbytes>: 32bit,表示ziplist占用内存的字节总数(也包括<zlbytes>本身占用的4个字节),在对压缩列表进行重新内存分配,或者计算zllen的位置时使用;
  • <zltail>: 32bit,表示ziplist表中最后一项(entry)的起始偏移量在ziplist中的起始地址有多少字节。<zltail>的存在,使得我们可以很方便地找到最后一项(不用遍历整个ziplist),从而可以在ziplist尾端快速地执行push或pop操作。
  • <zllen>: 16bit, 表示ziplist中数据项(entry)的个数。zllen字段因为只有16bit,所以可以表达的最大值为2^16-1。这里需要特别注意的是,如果ziplist中数据项个数超过了16bit能表达的最大值,ziplist仍然可以来表示。那怎么表示呢?这里做了这样的规定:如果<zllen>小于等于2^16-2(也就是不等于2^16-1),那么<zllen>就表示ziplist中数据项的个数;否则,也就是<zllen>等于16bit全为1的情况,那么<zllen>就不表示数据项个数了,这时候要想知道ziplist中数据项总数,那么必须对ziplist从头到尾遍历各个数据项,才能计数出来。
  • <entry>: 表示真正存放数据的数据项,长度不定。一个数据项(entry)也有它自己的内部结构,这个稍后再解释。
  • <zlend>: ziplist最后1个字节,是一个结束标记,值固定等于255。

上面的定义中还值得注意的一点是:<zlbytes><zltail><zllen>既然占据多个字节,那么在存储的时候就有大端(big endian)和小端(little endian)的区别。ziplist采取的是小端模式来存储,这在下面我们介绍具体例子的时候还会再详细解释。

redisson getlist 什么结构 redis底层数据结构_redis_05

redisson getlist 什么结构 redis底层数据结构_redis_06