Redis源码解析——压缩列表ziplist
在Redis中,list有两种存储方式:双端链表(LinkedList)和压缩双端链表(ziplist)。双端链表即普通数据结构中遇到的,在adlist.h和adlist.c中实现。压缩双端链表在ziplist.h和ziplist.c实现。
1. 定义
在redisObject一文中我们得知redis的hash底层存储可以使用ziplist和hashtable两种编码方式。当hash对象可以同时满足以下两个条件时,hash对象采样点ziplist编码。
- 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
- 哈希对象保存的键值对数量小于512个
ziplist包括zip header、zip entry和zip end三部分组成,其存储格式可以用下图表示。
zip_bytes是zip list占用的空间,zip_tail是最后一个数据项的偏移地址,方便反向遍历链表。zip_length表示zip_entry的个数,zip_end为定值0xFF,占1字节。
zip_entry的结构如下:
1 | //保存 ziplist 节点信息的结构 |
prevlen
ziplist在编码前置节点长度的时候,采用以下规则。1.如果前置节点的长度小于254字节,那么采用1个字节来保存这个长度值;2. 如果前置节点的长度大于254字节,则采用5个字节来保存这个长度值,其中,第一个字节被设置为0xFE(254),用于表示该长度大于254字节,后面四个字节则用来存储前置节点的长度值。
编码前置长度
- 如果前置节点的长度大于254字节,则采用5个字节来保存这个长度值,其中,第一个字节被设置为0xFE(254),用于表示该长度大于254字节,后面四个字节则用来存储前置节点的长度值。
1 | /* Encode the length of the previous entry and write it to "p". Return the |
解码前置节点长度
1 | /* Decode the number of bytes required to store the length of the previous |
在编码解码当前节点的长度,ziplist提供了zipEncodeLength和ZIP_DECODE_LENGTH这两个配套函数来完成。
2. 主要方法
2.1 创建空ziplist
1 | unsigned char *ziplistNew(void) { |
2.2 插入节点
1 | /* |
总结
压缩双链表以连续内存空间表示双链表,压缩双链表节省前驱和后驱指针的空间(共8B),这在小的list上,压缩效率是非常明显的,因为在一个普通的双链表中,前驱后驱指针在64位机器上需要分别占用8B
使用ziplist比hashtable更省内存,ziplist的连续内存可以利用cpu的缓存特性(hashtable是非线性的),某些情况下查找效率更高。