Redis的列表包含了双向链表和压缩列表,盘它
列表常见命令
- 右进左出
// 从右变添加三个元素
127.0.0.1:6379> rpush name "tom" "peter" "jack"
3
// 统计长度
127.0.0.1:6379> llen name
3
// 从左边弹出,类似队列,先进先出
127.0.0.1:6379> lpop name
tom
// 弹出了tom 这个值,所以现在长度是2,以下类推
127.0.0.1:6379> llen name
2
127.0.0.1:6379> lpop name
peter
127.0.0.1:6379> llen name
1
127.0.0.1:6379> lpop name
jack
127.0.0.1:6379> llen name
0
- 右进右出
// 清空name的键
127.0.0.1:6379> del name
0
// 从右边 添加三个元素
127.0.0.1:6379> rpush name "tom" "peter" "jack"
3
// 右边弹出,类似 栈,先进后出
127.0.0.1:6379> rpop name
jack
27.0.0.1:6379> llen name
2
127.0.0.1:6379> rpop name
peter
127.0.0.1:6379> rpop name
tom
127.0.0.1:6379> llen name
0
- 遍历元素
127.0.0.1:6379> rpush name "tom" "peter" "jack"
3
// 获取第一个元素
127.0.0.1:6379> lindex name 0
tom
127.0.0.1:6379> lindex name 1
peter
// lindex和pop区别,lindex只是读取数据,pop后,元素就回收了
127.0.0.1:6379> lindex name 0
tom
// 获取范围内的元素,-1表示倒数第一,即获取所有元素
127.0.0.1:6379> lrange name 0 -1
tom
peter
jack
// 保留区间内的,其他的删除。
127.0.0.1:6379> ltrim name 1 -1
OK
127.0.0.1:6379> llen name
2
127.0.0.1:6379> lrange name 0 -1
peter
jack
数据结构
双向链表
typedef struct list {
// 头结点
listNode *head;
// 尾结点
listNode *tail;
// 复制链表节点所保存的值
void *(*dup)(void *ptr);
// 释放链表节点所保存的值
void (*free)(void *ptr);
// 对比链表节点所保存的值和另一个输入值是否相等
int (*match)(void *ptr, void *key);
// 链表的节点数量
unsigned long len;
} list;
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
压缩列表
- previous_entry_length:前一个元素的字节长度,如果前一个元素长度小于254字节,该属性用1字节表示。如果大于等于254字节时,则用5个字节表示。
- encoding:entry存储数据的编码(整数或字节数组)
- content:表示该元素实际存储的内容
typedef struct zlentry {
// previous_entry_length字段的长度(即 1个字节还是5个字节)
unsigned int prevrawlensize;
// previous_entry_length的值
unsigned int prevrawlen;
// 当前元素的编码长度
unsigned int lensize;
// 当前元素的实际长度
unsigned int len;
// 当前元素首部长度 (prevrawlensize+lensize)
unsigned int headersize;
// 编码类型
unsigned char encoding;
unsigned char *p;
} zlentry;
内存分配
由于压缩列表的插入元素 会导致后面元素previous_entry_length可能会从1个字节变成5个字节,也能从5个字节变成1个字节。
size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl));
int forcelarge = 0;
nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
if (nextdiff == -4 && reqlen < 4) {
nextdiff = 0;
forcelarge = 1;
}
//存储偏移量
offset = p-zl;
//调用realloc重新分配空间
zl = ziplistResize(zl,curlen+reqlen+nextdiff);
//重新偏移到插入位置P
p = zl+offset;
nextdiff表示插入元素时,下个元素需要变化的字节长度 (4 、0 、-4 )。
由于entryX+1开始是1个字节,因为entryNew插入后,entryX+1的previous_entry_length需要变成5个字节。因此需要重新计算插入位置的地址。以及后面元素需要后移(nextdiff+entryNew长度)字节。
连锁更新
如果一个压缩列表的每个entry的大小都是在253-254个字节。如果此时在中间插入一个entryNew并且大小大约254。那么后面一个entryN+1的previous_entry_length的值都要增加4个字节。从而引发entryN+2的previous_entry_length也要增加。
至于对于这种情况,官方回答就是概率太低。