Redis数据类型之List(三)
前言:list即链表,它是一个能维持数据先后顺序的列表,便于在表的两端追加和删除数据,中间位置的存取具有O(N)的时间复杂度,是一个双向链表。
一、内部原理
redis内部实现代码在quicklist.c(注释:A doubly linked list of ziplists)中,它确实是一个双向链表,并且是一个ziplist双向列表。
ziplist是什么?
一个经过特殊编码的的双向链表,它的设计目的是为了提高存储效率。ziplist可以用于存储字符串或整数,其中整数是真正的二进制进行编码的,而不是编码成字符串序列。普通的双向链表每一项都独立的占用一块内存,各项之间用地址指针连接起来。这中方式会带来大量的内存碎片,而且地址指针也会占用额外的内存。而ziplist将列表的每一项存放在前后连续的地址空间内,一个大的ziplist整体占用一大块内存,它是一个列表,但不是一个链表。ziplist为了在细节上节省内存,对于值的存储采用了变长的编码方式,对于大的整数,就多一些字节来存储,对于小的少一些字节来存储。
ziplist的数据结构如下:
<zlbytes><zltail><zllen><entry>...<entry><zlend>
含义:
<zlbytes>:32字节,表示ziplist占用的字符总数(本身占用4个字节)。
<zltail>: 32字节,表示ziplist表中最后一项(entry)在ziplist中的偏移字节数。<zltail>的存在,使得我们可以很方便地找到最后一项,从而可以在ziplist尾端快速地执行push或pop操作。
<zllen> : 16字节, 表示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。
entry的数据结构:
<prevrawlen><len><data>
<prevrawlen>: 表示前一个数据项占用的总字节数。这个字段的用处是为了让ziplist能够从后向前遍历(从后一项的位置,只需向前偏移prevrawlen个字节,就找到了前一项)。这个字段采用变长编码。
<prevrawlen>。它有两种可能,或者是1个字节,或者是5个字节:
1. 如果前一个数据项占用字节数小于254,那么<prevrawlen>就只用一个字节来表示,这个字节的值就是前一个数据项的占用字节数。
2. 如果前一个数据项占用字节数大于等于254,那么<prevrawlen>就用5个字节来表示,其中第1个字节的值是254(作为这种情况的一个标记),而后面4个字节组成一个整型值,来真正存储前一个数据项的占用字节数。
<len>: 表示当前数据项的数据长度(即<data>部分的长度)。也采用变长编码。根据第一个字节的不同分为下面九种方式
|00pppppp| - 1 byte String value with length less than or equal to 63 bytes (6 bits).
|01pppppp|qqqqqqqq| - 2 bytes String value with length less than or equal to 16383 bytes (14 bits).
|10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes String value with length greater than or equal to 16384 bytes.
|11000000| - 1 byte Integer encoded as int16_t (2 bytes).
|11010000| - 1 byte Integer encoded as int32_t (4 bytes).
|11100000| - 1 byte Integer encoded as int64_t (8 bytes).
|11110000| - 1 byte Integer encoded as 24 bit signed (3 bytes).
|11111110| - 1 byte Integer encoded as 8 bit signed (1 byte).
|1111xxxx| - (with xxxx between 0000 and 1101) immediate 4 bit integer. Unsigned integer from 0 to 12. The encoded value is actually from 1 to 13 because 0000 and 1111 can not be used, so 1 should be subtracted from the encoded 4 bit value to obtain the right value.
|11111111| - End of ziplist.
quicklist是什么?
双向链表都是有多个node组成,而quicklist的每个节点都是一个ziplist。ziplist本身也是一个能维持数据项先后顺序的列表,而且内存是紧凑的,例如一个包含2个node的quicklist,如果每个节点的ziplist包含了四个数据项
那么对外表现就是8个数据项。quicklist的设计是一个空间和时间的折中,双向链表便于在表的两端进行push和pop操作,但是它的内存开销很大。开销如下
1.每个节点上除了要保存数据之外,还要额外的保存两个指针。
2.各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。
ziplist是一块连续的内存,所以存储效率很高。但是,它不利于修改操作,每次数据变动都会引发内存的realloc。一次realloc可能会导致大量的数据拷贝,进一步降低性能。
quicklist结合了双向链表和ziplist的优点,但是同样也存在一个问题,一个quicklist包含多长的ziplist合适呢?需要找到一个平衡点
1.ziplist太短,内存碎片越多。
2.ziplist太长,分配大块连续内存空间的难度就越大。
如果保持ziplist的合理长度,取决于具体的应用场景。redis提供了默认配置
list-max-ziplist-size -2
参数的含义解释,取正值时表示quicklist节点ziplist包含的数据项。取负值表示按照占用字节来限定quicklist节点ziplist的长度。
-5: 每个quicklist节点上的ziplist大小不能超过64 Kb。
-4: 每个quicklist节点上的ziplist大小不能超过32 Kb。
-3: 每个quicklist节点上的ziplist大小不能超过16 Kb。
-2: 每个quicklist节点上的ziplist大小不能超过8 Kb。(默认值)
-1: 每个quicklist节点上的ziplist大小不能超过4 Kb。
list设计最容易被访问的是列表两端的数据,中间的访问频率很低,如果符合这个场景,list还有一个配置,可以对中间节点进行压缩(采用的LZF——一种无损压缩算法),进一步节省内存。配置如下
list-compress-depth 0
含义:
0: 是个特殊值,表示都不压缩。这是Redis的默认值。
1: 表示quicklist两端各有1个节点不压缩,中间的节点压缩。
2: 表示quicklist两端各有2个节点不压缩,中间的节点压缩。
以此类推
quicklist数据结构:
/* quicklistNode is a 32 byte struct describing a ziplist for a quicklist.
* We use bit fields keep the quicklistNode at 32 bytes.
* count: 16 bits, max 65536 (max zl bytes is 65k, so max count actually < 32k).
* encoding: 2 bits, RAW=1, LZF=2.
* container: 2 bits, NONE=1, ZIPLIST=2.
* recompress: 1 bit, bool, true if node is temporarry decompressed for usage.
* attempted_compress: 1 bit, boolean, used for verifying during testing.
* extra: 12 bits, free for future use; pads out the remainder of 32 bits */
typedef struct quicklistNode {
struct quicklistNode *prev; /*指向链表前一个节点的指针*/
struct quicklistNode *next; /*指向链表后一个节点的指针*/
unsigned char *zl;/*数据指针。如果当前节点的数据没有压缩,那么它指向一个ziplist结构;否则,它指向一个quicklistLZF结构。*/
unsigned int sz; /*表示zl指向的ziplist的总大小(包括zlbytes, zltail, zllen, zlend和各个数据项)。需要注意的是:如果ziplist被压缩了,那么这个sz的值仍然是压缩前的ziplist大小。/*
unsigned int count : 16; /* 表示ziplist里面包含的数据项个数。 */
unsigned int encoding : ; /* RAW==1(未压缩) or LZF==2 (压缩了并采用LZF压缩算法)*/
unsigned int container : ; /* 使用的容器 NONE==1 or ZIPLIST==2(默认值) */
unsigned int recompress : ; /* 我们使用类似lindex这样的命令查看了某一项本来压缩的数据时,需要把数据暂时解压,这时就设置recompress=1做一个标记,等有机会再把数据重新压缩 */
unsigned int attempted_compress : ; /* node can't compress; too small */
unsigned int extra : ; /* 其他扩展字段(未使用) */
} quicklistNode; /* quicklistLZF is a 4+N byte struct holding 'sz' followed by 'compressed'.
* 'sz' is byte length of 'compressed' field.
* 'compressed' is LZF data with total (compressed) length 'sz'
* NOTE: uncompressed length is stored in quicklistNode->sz.
* When quicklistNode->zl is compressed, node->zl points to a quicklistLZF */
typedef struct quicklistLZF {
unsigned int sz; /* 表示压缩后的ziplist大小*/
char compressed[]; /*是个柔性数组(flexible array member),存放压缩后的ziplist字节数组/*
} quicklistLZF; /* quicklist is a 32 byte struct (on 64-bit systems) describing a quicklist.
* 'count' is the number of total entries.
* 'len' is the number of quicklist nodes.
* 'compress' is: -1 if compression disabled, otherwise it's the number
* of quicklistNodes to leave uncompressed at ends of quicklist.
* 'fill' is the user-requested (or default) fill factor. */
typedef struct quicklist {
quicklistNode *head; ?/*指向头节点(左侧第一个节点)的指针。*/
quicklistNode *tail; /*指向尾节点(右侧第一个节点)的指针。*/
unsigned long count; /* quicklist节点的个数 */
unsigned int len; /* number of quicklistNodes */
int fill : ; /* ziplist大小设置,存放list-max-ziplist-size参数的值 */
unsigned int compress : ; /* 节点压缩深度设置,存放list-compress-depth参数的值 */
}
二:相关命令
lpush key value[value...] 将一个或多个value插入到列表的表头,如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表头: 比如说,对空列表 mylist 执行命令 LPUSH mylist a b c,列表的值将是 c b a ,这等同于原子性地执行 LPUSH mylist a 、 LPUSH mylist b 和 LPUSH mylist c 三个命令。如果 key 不存在,一个空列表会被创建并执行 lpush 操作。当 key 存在但不是列表类型时,返回一个错误。
lpushx key value 将值 value 插入到列表 key 的表头,若key不存在,不执行任何操作。
lpop key 移除并返回列表key的头元素(后进先出),若key不存在返回nil。
blpop key[key...] timeout lpop的阻塞版本,若给定列表中没有任何元素可供弹出时,链接会被blpop命令阻塞,直到等待超时(单位:秒)或发现可弹出元素时为止,若发现其中任何一个列表中有值则返回列表key和第一个元素的值。
rpush key value[value...] 将一个或多个值 value 插入到列表 key 的表尾(最右边),如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表尾:比如对一个空列表 mylist 执行 RPUSH mylist a b c ,得出的结果列表为 a b c ,等同于执行命令 RPUSH mylist a 、 RPUSH mylist b 、 RPUSH mylist c 。如果 key 不存在,一个空列表会被创建并执行 Rpush 操作。当 key 存在但不是列表类型时,返回一个错误。
rpushx key value 将值 value 插入到列表 key 的表尾,若key不存在,不执行任何操作。
rpop key 移除并返回列表的末尾,若key不存在则返回nil。
brpop key[key...] timeout 它是 rpop命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被 brpop 命令阻塞,直到等待超时或发现可弹出元素为止。当给定多个 key 参数时,按参数 key 的先后顺序依次检查各个列表,弹出第一个非空列表的尾.
rpoplpush source destination 命令 rpoppush 在一个原子时间内,执行以下两个动作:将列表 source 中的最后一个元素(尾元素)弹出,并返回给客户端。将 source 弹出的元素插入到列表 destination ,作为 destination 列表的的头元素。如果 source 不存在,值 nil 被返回,并且不执行其他动作。如果 source 和 destination 相同,则列表中的表尾元素被移动到表头,并返回该元素,可以把这种特殊情况视作列表的旋转(rotation)操作。
brpoplpush source destination brpoplpush是 rpoplpush的阻塞版本,当给定列表 source 不为空时, brpoplpush 的表现和 rpoplpush 一样。当列表 source 为空时, brpoplpush 命令将阻塞连接,直到等待超时,或有另一个客户端对 source 执行 lpush或 rpush 命令为止。超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely)
lset key index value 将列表 key 下标为 index 的元素的值设置为 value 。当 index 参数超出范围,或对一个空列表( key 不存在)进行 lset时,返回一个错误。
linsert key before|after pivot value 将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。当 pivot 不存在于列表 key 时,不执行任何操作。当 key 不存在时, key 被视为空列表,不执行任何操作。如果 key 不是列表类型,返回一个错误。
llen key 返回列表 key 的长度。如果 key 不存在,则 key 被解释为一个空列表,返回 0 .如果 key 不是列表类型,返回一个错误。
lindex key index 返回列表 key 中,下标为 index 的元素。下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。如果 key 不是列表类型,返回一个错误。
lrange key start stop 返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定。下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
ltrim key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。当 key 不是列表类型时,返回一个错误。
lrem key count value 移除列表中与value相等的元素,若count>0从左到右移除与count个与value相等的元素;若count<0从右向左移除count个与value相等的元素;若count==0移除所有与value相等的元素。
Redis数据类型之List(三)的更多相关文章
- Redis(三):Redis数据类型
Redis数据类型目录导航: Redis五大数据类型 哪里去获取Redis常见数据类型操作命令 Redis键(Key) Redis字符串(String) Redis列表(List) Redis集合(S ...
- 初识redis数据类型
初识redis数据类型 1.String(字符串) string是redis最基本的类型,一个key对应一个value. string类型是二进制安全的.意思是redis的string可以包含任何数据 ...
- Redis 小白指南(三)- 事务、过期、消息通知、管道和优化内存空间
Redis 小白指南(三)- 事务.过期.消息通知.管道和优化内存空间 简介 <Redis 小白指南(一)- 简介.安装.GUI 和 C# 驱动介绍> 讲的是 Redis 的介绍,以及如何 ...
- redis笔记总结之redis数据类型及常用命令
三.常用命令 3.1 字符串类型(string) 字符串类型是Redis中最基本的数据类型,一个字符串类型的键允许存储的数据的最大容量为512MB. 3.1.1 赋值与取值: SET key valu ...
- 缓存数据库-redis数据类型和操作(list)
转: 狼来的日子里! 奋发博取 缓存数据库-redis数据类型和操作(list) 一:Redis 列表(List) Redis列表是简单的字符串列表,按照插入顺序排序.你可以添加一个元素导列表的头部( ...
- Redis 基础:Redis 数据类型
Redis 数据类型 Redis支持五种数据类型:string(字符串).hash(哈希).list(列表).set(集合)及zset(sorted set:有序集合). String(字符串) st ...
- 二:Redis数据类型
一.nosql(非关系性数据库): mongoDB hbase redis nulch hive pig mahout zookeeper 二:redis 数据类型 1.存储string: 常用命令 ...
- Redis数据类型和操作
<"Java技术员"成长手册>,包含框架.存储.搜索.优化.分布式等必备知识,都收集在GitHub JavaEgg ,N线互联网开发必备技能兵器谱,欢迎指导 Redis ...
- NoSql数据库Redis系列(2)——Redis数据类型
一.设计 Redis Key (一).分段设计法 使用冒号把 key 中要表达的多种含义分开表示,步骤如下: 1.把表名转化为 key 前缀 2.主键名(或其他常用于搜索的字段) 3.主键值 4.要存 ...
- redis数据类型及订阅操作
Redis数据类型详解 Redis键/值介绍 Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值,从形如“foo”的简单字符串到一个JPG文件的内容都可以.空字符串也是有效k ...
随机推荐
- 系统启动 之 Linux系统启动概述(1)
随着智能终端功能的越来越庞大,与之,硬件配置越来越高,开机时间却越来越长.人们在享受强大功能的同时,对冗长的智能终端的开机时间却越来越缺乏耐心. 为了"取悦"用户,需要提供较好的用 ...
- java线程(三)
线程代码同步与线程锁 为什么要有同步代码块? 线程同步的出现是为了解决多个线程对统一资源操作而引发的数据混乱问题.这里引用一个经典demo-银行转账操作,场景如下,小明的账户目前有1000人民币,他在 ...
- Java中的增强 for 循环 foreach
foreach 是 Java 中的一种语法糖,几乎每一种语言都有一些这样的语法糖来方便程序员进行开发,编译期间以特定的字节码或特定的方式来对这些语法进行处理.能够提高性能,并减少代码出错的几率.在 J ...
- [ios]quartz2d画板功功能实现核心代码
//触摸开始 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ // 1,获取对 ...
- Java (PO,VO,DAO,BO,POJO,DTO) 几种对象解释
1. PO:persistant object 持久对象 可以看成是与数据库中的表相映射的java对象.最简单的PO就是对应数据库中某个表中的一条记录,多个记录可以用PO的集合.PO中应该不包含任何对 ...
- Spring 日期时间处理
1 Spring自身的支持 1.1 factory-bean <bean id="dateFormat" class="java.text.SimpleDateFo ...
- SecureCRT通过Javascrip脚本实现Ctrl+C
代码如下: # $language = "JScript" # $interface = "1.0" for( i=1 ; i < 3600 ; i++) ...
- 如何自学成为一个WEB前端
WEB前端是做什么的? 那些什么高大上的介绍作者就略过了,简单来说就是做网页的,我们上网浏览的网站界面就是WEB前端工程师做的. 在互联网迅速发展的近几年,你上网冲浪的时候是不是感觉WEB网站越来越漂 ...
- Spring-web中的web.xml为Servlet提供的配置选项说明
配置Servlet时可以设置的一些初始化参数,总结如下: ContextAttribute: 在ServletContext的属性中,要用作WebApplicationContext的属性名称. Co ...
- 016 多对多关联映射 单向(many-to-many)
一般的设计中,多对多关联映射,需要一个中间表 Hibernate会自动生成中间表 Hibernate使用many-to-many标签来表示多对多的关联 多对多的关联映射,在实体类中,跟一对多一样,也是 ...