前言: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(三)的更多相关文章

  1. Redis(三):Redis数据类型

    Redis数据类型目录导航: Redis五大数据类型 哪里去获取Redis常见数据类型操作命令 Redis键(Key) Redis字符串(String) Redis列表(List) Redis集合(S ...

  2. 初识redis数据类型

    初识redis数据类型 1.String(字符串) string是redis最基本的类型,一个key对应一个value. string类型是二进制安全的.意思是redis的string可以包含任何数据 ...

  3. Redis 小白指南(三)- 事务、过期、消息通知、管道和优化内存空间

    Redis 小白指南(三)- 事务.过期.消息通知.管道和优化内存空间 简介 <Redis 小白指南(一)- 简介.安装.GUI 和 C# 驱动介绍> 讲的是 Redis 的介绍,以及如何 ...

  4. redis笔记总结之redis数据类型及常用命令

    三.常用命令 3.1 字符串类型(string) 字符串类型是Redis中最基本的数据类型,一个字符串类型的键允许存储的数据的最大容量为512MB. 3.1.1 赋值与取值: SET key valu ...

  5. 缓存数据库-redis数据类型和操作(list)

    转: 狼来的日子里! 奋发博取 缓存数据库-redis数据类型和操作(list) 一:Redis 列表(List) Redis列表是简单的字符串列表,按照插入顺序排序.你可以添加一个元素导列表的头部( ...

  6. Redis 基础:Redis 数据类型

    Redis 数据类型 Redis支持五种数据类型:string(字符串).hash(哈希).list(列表).set(集合)及zset(sorted set:有序集合). String(字符串) st ...

  7. 二:Redis数据类型

    一.nosql(非关系性数据库): mongoDB hbase redis nulch hive pig mahout zookeeper 二:redis 数据类型 1.存储string: 常用命令 ...

  8. Redis数据类型和操作

    <"Java技术员"成长手册>,包含框架.存储.搜索.优化.分布式等必备知识,都收集在GitHub JavaEgg ,N线互联网开发必备技能兵器谱,欢迎指导 Redis ...

  9. NoSql数据库Redis系列(2)——Redis数据类型

    一.设计 Redis Key (一).分段设计法 使用冒号把 key 中要表达的多种含义分开表示,步骤如下: 1.把表名转化为 key 前缀 2.主键名(或其他常用于搜索的字段) 3.主键值 4.要存 ...

  10. redis数据类型及订阅操作

    Redis数据类型详解 Redis键/值介绍 Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值,从形如“foo”的简单字符串到一个JPG文件的内容都可以.空字符串也是有效k ...

随机推荐

  1. 给上传文件的input控件"美容"

    作为一名前端程序猿呢,在工作中经常会遇到form表单这种东西.然而表单的其他input控件样式还是很好改变的.但是,唯独input类型是file的文件上传控件可能就没那么好打扮的漂亮.刚好菜鸟我最近工 ...

  2. [Linux] Linux 中的基本命令与目录结构

    Linux 中的基本命令与目录结构 目录 一.Linux 基本目录结构 二.基本命令 三.浏览目录 四.中间命令 五.更改密码 六.环境变量和 shell 变量 七.命令路径 八.文本编辑器 九.获取 ...

  3. ListView在异步加载动态图片时,往往最后一项或几项被遮盖(IM场景居多)

    如果ListView中得默认图片比较小,新图片加载后,撑大ListView中的对应项,导致最后一项或几项被覆盖. 解决思路: 1.默认图片设定和新图大小一样,换句话说,新图加载后转成和默认图片一样的大 ...

  4. Android ViewPager动画切换

    使用方法 setPageTransformer 例如: ViewPager.setPageTransformer(true, new ZoomOutPageTransformer()) package ...

  5. Spring+SpringMVC+MyBatis+easyUI整合优化篇(十四)谈谈写博客的原因和项目优化

    阶段总结 又到了优化篇的收尾阶段了,这其实是一篇阶段总结性的文章,今天是4月29号,距离第一次发布博客已经两个月零5天,这两个多月的时间,完成了第一个项目ssm-demo的更新,过程中也写了33篇博客 ...

  6. 斗牛app上架应用宝、牛牛手机游戏推广、百人牛牛app应用开发、棋牌游戏上传、手游APP优化

    联系QQ:305-710439斗牛app上架应用宝.牛牛手机游戏推广.百人牛牛app应用开发.棋牌游戏上传.手游APP优化 iOS开发iPhone/iPad平台安卓手机软件开发机型覆盖范围 超过113 ...

  7. poj1797 Heavy Transportation Dijkstra算法的简单应用

    题目链接:http://poj.org/problem?id=1797 题目就是求所有可达路径的其中的最小值边权的最大值 即对于每一条能够到达的路径,其必然有其最小的承载(其实也就是他们自身的最大的承 ...

  8. 深入理解JAVA序列化

    如果你只知道实现 Serializable 接口的对象,可以序列化为本地文件.那你最好再阅读该篇文章,文章对序列化进行了更深一步的讨论,用实际的例子代码讲述了序列化的高级认识,包括父类序列化的问题.静 ...

  9. [大数据]-Elasticsearch5.3.1 IK分词,同义词/联想搜索设置

    --题外话:最近发现了一些问题,一些高搜索量的东西相当一部分没有价值.发现大部分是一些问题的错误日志.而我是个比较爱贴图的.搜索引擎的检索会将我们的博文文本分词.所以图片内容一般是检索不到的,也就是说 ...

  10. winows 服务器环境搭建 (碰到了windows服务器,小记一下吧~)

    1.连接远程服务器  安装wamp 2.查看wamp 默认端口号是否与对应windows 服务器冲突,如果冲突,则改之 WAMP装好之后默认的端口是80,但是这个80端口呢,可以热门端口啊,迅雷,II ...