列表对象

列表对象的编码可以是ziplist或者linkedlist,ziplist编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点(entry)保存了一个列表元素。举个栗子,如果我们执行RPUSH命令,那么服务器将创建一个列表对象作为numbers键的值:

127.0.0.1:6379> RPUSH numbers 1 "three" 5
(integer) 3

  

如果numbers键的键值对使用的是ziplist编码,这个值对象将会是图1-5所示的样子

图1-5   ziplist编码的numbers列表对象

另一方面,linkedlist编码的列表对象使用双端链表作为底层实现,每个双端链表节点(node)都保存了一个字符串对象,而每个字符串对象都保存了一个列表元素。举个栗子,如果前面说的numbers键创建的列表对象使用的不是ziplist编码,而是linkedlist编码,那么numbers键的值对象将是图1-6所示的样子

图1-6   linkedlist编码的numbers列表对象

注意,linkedlist编码的列表对象在底层的双端链表结构中包含了多个字符串对象,这种嵌套字符串对象的行为在稍后介绍的哈希对象、集合对象和有序集合对象中都会出现,字符串对象时Redis五种类型的对象中的唯一一种会被其他四种类型对象嵌套的对象

注:为了简化字符串对象的表示,我们在图1-6使用了一种带有StringObject字样的格子来表示一个字符串对象,而StringObject字样下面的是字符串对象所保存的值。比如图1-7代表的就是一个包含字符串"three"的字符串对象,它是图1-8的简化表示:

图1-7   简化的字符串对象表示

图1-8   完整的字符串对象表示

编码转换

当列表对象可以同时满足以下两个条件时,列表对象使用ziplist编码:

  • 列表对象保存的所有字符串元素的长度都小于64字节
  • 列表对象保存的元素数量小于512个

不能满足以上两个条件的列表需要使用linkedlist编码,当然,以上两个条件的上限是可以修改的,具体看配置文件中关于list-max-ziplist-value选项和list-max-ziplist-entries选项的说明

对于使用ziplist编码的列表对象来说,当使用ziplist编码所需的两个条件的任意一个不能被满足时,对象的编码转换操作就会被执行,原本保存在压缩列表里的所有列表元素都会被转移并保存到双端链表里面,对象的编码也会从ziplist变为linkedlist。以下代码展示了列表对象因保存了长度太大的元素而进行编码转换的情况

# 所有元素的长度都小于 64 字节
127.0.0.1:6379> RPUSH blah "hello" "world" "again"
(integer) 3 127.0.0.1:6379> OBJECT ENCODING blah
"ziplist" # 将一个 65 字节长的元素推入列表对象中
127.0.0.1:6379> RPUSH blah "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"
(integer) 4 # 编码已改变
127.0.0.1:6379> OBJECT ENCODING blah
"linkedlist"

  

除此之外,以下代码展示了列表对象因为保存的元素对象过多而进行编码转换的情况:

# 列表对象包含 512 个元素
127.0.0.1:6379> EVAL "for i=1,512 do redis.call('RPUSH', KEYS[1], i) end" 1 "integers"
(nil)
127.0.0.1:6379> LLEN integers
(integer) 512
127.0.0.1:6379> OBJECT ENCODING integers
"ziplist"
# 再向列表对象推入一个新元素,使得对象保存的元素数量达到 513 个
127.0.0.1:6379> RPUSH integers 513
(integer) 513
# 编码已改变
127.0.0.1:6379> OBJECT ENCODING integers
"linkedlist"

    

列表命令的实现

因为列表键为列表对象,所以用于列表键的所有命令都是针对列表对象来构建的,表1-8列出了其中一部分列表键的命令,以及这些命令在不同编码的列表对象下的实现方法

表1-8   列表命令的实现
命令 ziplist编码的实现方法 linkedlist编码的实现方法
LPUSH  调用ziplistPush函数,将新元素推入到压缩列表的表头 调用listAddNodeHead函数,将新元素推入到双端链表的表头
RPUSH  调用ziplistPush函数,将新元素推入到压缩列表的表尾  调用listAddNodeTail函数,将新元素推入到双端链表的表尾
LPOP  调用ziplistIndex函数定位压缩列表的表头节点,在向用户返回节点所保存的元素之后,调用ziplistDelete函数删除表头节点  调用listFirst 函数定位双端链表的表头节点,在向用户返回节点所保存的元素之后,调用listDelNode函数删除表头节点 
RPOP  调用ziplistIndex函数定位压缩列表的表尾节点,在向用户返回节点所保存的元素之后,调用ziplistDelete函数删除表尾节点  调用listLast 函数定位双端链表的表尾节点,在向用户返回节点所保存的元素之后,调用listDelNode函数删除表尾节点
LINDEX  调用ziplistIndex函数定位压缩列表中的指定节点,然后返回节点所保存的元素  调用listIndex函数定位双端链表中的指定节点,然后返回节点所保存的元素 
LLEN  调用ziplistLen函数返回压缩列表的长度  调用listLength函数返回双端链表的长度 
LINSERT  插入新节点到压缩列表的表头或者表尾时,使用ziplistPush函数;插入新节点到压缩列表的其他位置时,使用ziplistInsert函数 调用listInsertNode函数, 将新节点插入到双端链表的指定位置 
LREM  遍历压缩列表节点,并调用ziplistDelete函数删除包含了给定元素的节点 遍历双端链表节点,并调用listDelNode函数删除包含了给定元素的节点
LTRIM  调用ziplistDeleteRange函数,删除压缩列表中所有不在指定索引范围内的节点  遍历双端链表节点,并调用listDelNode函数删除链表中所有不在指定索引范围内的节点 
LSET   调用ziplistDelete函数,先删除压缩列表指定索引上的现有节点,然后调用ziplistInsert函数,将一个包含给定元素的新节点插入到相同索引上面 调用listIndex函数,定位到双端链表指定索引上的节点,然后通过赋值操作更新节点的值

哈希对象

哈希对象的编码可以是ziplist或者hashtable,ziplist编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表的表尾,然后再将保存了值的压缩列表节点推入到压缩列表表尾,因此:

  • 保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后
  • 先添加到哈希对象中的键值对会被放在压缩列表的表头方向,而后来添加到哈希对象中的键值对被放在压缩列表的表尾

举个栗子,如果我们执行以下HSET命令,那么服务器将创建一个列表对象作为profile键的值:

127.0.0.1:6379> HSET profile name "Tom"
(integer) 1
127.0.0.1:6379> HSET profile age 25
(integer) 1
127.0.0.1:6379> HSET profile career "Programmer"
(integer) 1
127.0.0.1:6379> OBJECT ENCODING profile
"ziplist"

  

如上述示例所见,profile键的值对象使用的是ziplist编码,这个值对象是图1-9所示的样子,其中对象所使用的压缩列表如图1-10所示

图1-9   ziplist编码的profile哈希对象

图1-10   profile哈希对象的压缩列表底层实现

另一方面,hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对来保存:

  • 字典的每个键都是一个字符串对象,对象中保存了键值对的键
  • 字典的每个值都是一个字符串对象,对象中保存了键值对的值

举个栗子,如果前面的profile键创建的不是ziplist编码的哈希对象,而是hashtable编码的哈希对象,那么这个哈希对象应该是如图1-11所示的样子

图1-11   hashtable编码的profile哈希对象

编码转换

当哈希对象可以同时满足以下两个条件时,哈希对象使用ziplist编码:

  • 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
  • 哈希对象保存的键值对数量小于512个

不能满足这两个条件的哈希对象需要使用hashtable编码,以上两个条件的上限值是可以修改的,具体请看配置文件中关于hash-max-ziplist-value选项和hash-max-ziplist-entries选项的说明

对于使用ziplist编码的列表对象来说,当使用ziplist编码所需的两个条件的任意一个不能被满足时,对象的编码转换操作就会执行,原本保存在压缩列表里的所有键值对都会被转移并保存到字典里面,对象的编码也会从ziplist变为hashtable。以下代码展示了哈希对象因为键值对的键长度太大而引起编码转换的情况:

# 哈希对象只包含一个键和值都不超过 64 个字节的键值对
127.0.0.1:6379> HSET book name "Mastering C++ in 21 days"
(integer) 1
127.0.0.1:6379> OBJECT ENCODING book
"ziplist"
# 向哈希对象添加一个新的键值对,键的长度为 66 字节
127.0.0.1:6379> HSET book long_long_long_long_long_long_long_long_long_long_long_description "content"
(integer) 1
# 编码已改变
127.0.0.1:6379> OBJECT ENCODING book
"hashtable"

  

除了键的长度太大会引起编码转换外,值的长度太大也会引起编码转换

# 哈希对象只包含一个键和值都不超过 64 个字节的键值对
127.0.0.1:6379> HSET blah greeting "hello world"
(integer) 1
127.0.0.1:6379> OBJECT ENCODING blah
"ziplist"
# 向哈希对象添加一个新的键值对,值的长度为 68 字节
127.0.0.1:6379> HSET blah story "many string ... many string ... many string ... many string ... many"
(integer) 1
# 编码已改变
127.0.0.1:6379> OBJECT ENCODING blah
"hashtable"

  

最有,再展示哈希对象因为包含的键值对数量过多而引起的编码转换

# 创建一个包含 512 个键值对的哈希对象
127.0.0.1:6379> EVAL "for i=1, 512 do redis.call('HSET', KEYS[1], i, i) end" 1 "numbers"
(nil)
127.0.0.1:6379> HLEN numbers
(integer) 512
127.0.0.1:6379> OBJECT ENCODING numbers
"ziplist"
# 再向哈希对象添加一个新的键值对,使得键值对的数量变成 513 个
127.0.0.1:6379> HMSET numbers "key" "value"
OK
127.0.0.1:6379> HLEN numbers
(integer) 513
# 编码改变
127.0.0.1:6379> OBJECT ENCODING numbers
"hashtable"

  

哈希命令的实现

因为哈希键的值为哈希对象,所以用于哈希键的所有命令都是针对哈希键对象来构建的,表1-9列出了其中一部分哈希键的命令,以及这些命令在不同编码的哈希键对象下的实现方法:

表8-9   哈希命令的实现
命令 ziplist编码实现方法 hashtable编码的实现方法
HSET 首先调用ziplistPush函数,将键推入到压缩列表的表尾,然后再次调用ziplistPush函数,将值推入到压缩列表的表尾 调用dictAdd函数, 将新节点添加到字典里面
HGET 首先调用ziplistFind函数,在压缩列表中查找指定键所对应的节点,然后调用ziplistNext函数,将指针移动到键节点旁边的值节点, 最后返回值节点 调用dictFind函数,在字典中查找给定键,然后调用dictGetVal函数,返回该键所对应的值
HEXISTS 调用ziplistFind函数,在压缩列表中查找指定键所对应的节点,如果找到的话说明键值对存在,没找到的话就说明键值对不存在 调用dictFind函数,在字典中查找给定键,如果找到的话说明键值对存在,没找到的话就说明键值对不存在
HDEL 调用ziplistFind函数,在压缩列表中查找指定键所对应的节点,然后将相应的键节点、以及键节点旁边的值节点都删除掉 调用dictDelete 函数,将指定键所对应的键值对从字典中删除掉
HLEN 调用ziplistLen函数,取得压缩列表包含节点的总数量,将这个数量除以2,得出的结果就是压缩列表保存的键值对的数量 调用dictSize函数, 返回字典包含的键值对数量,这个数量就是哈希对象包含的键值对数量
HGETALL 遍历整个压缩列表,用ziplistGet函数返回所有键和值(都是节点) 遍历整个字典,用dictGetKey函数返回字典的键, 用dictGetVal函数返回字典的值

Redis实现之对象(二)的更多相关文章

  1. redis数据结构和对象二

    跳跃表(skiplist) 跳跃表是一种有序数据结构.跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,大部分情况下,跳跃表的效率可以和平衡树相媲美,并且因为跳跃表的实现比平衡树简单,所有不 ...

  2. Redis实战之征服 Redis + Jedis + Spring (二)

    不得不说,用哈希操作来存对象,有点自讨苦吃! 不过,既然吃了苦,也做个记录,也许以后API升级后,能好用些呢?! 或许,是我的理解不对,没有真正的理解哈希表. 相关链接: Redis实战 Redis实 ...

  3. Redis 小白指南(二)- 基础命令和五大类型:字符串、散列、列表、集合和有序集合

    Redis 小白指南(二)- 基础命令和五大类型:字符串.散列.列表.集合和有序集合 引言 目录 基础命令 字符串类型 散列类型 列表类型 集合类型 有序集合类型 基础命令 1.获得符合规则的键名列表 ...

  4. Redis 小白指南(二)- 聊聊五大类型:字符串、散列、列表、集合和有序集合

    Redis 小白指南(二)- 聊聊五大类型:字符串.散列.列表.集合和有序集合 引言 开篇<Redis 小白指南(一)- 简介.安装.GUI 和 C# 驱动介绍>已经介绍了 Redis 的 ...

  5. Redis源代码剖析--对象object

    前面一系列的博客分析了Redis的基本数据结构,有动态字符串sds.双端链表sdlist.字典dict.跳跃表skiplist.整数集合intset和压缩列表ziplist等,这些数据结构对于用户来说 ...

  6. 浅谈:Redis持久化机制(二)AOF篇

    浅谈:Redis持久化机制(二)AOF篇 ​ 上一篇我们提及到了redis的默认持久化方式RDB,是一种通过存储快照数据方式持久化的机制,它在宕机后会丢失掉最后一次更新RDB文件后的数据,这也是由于它 ...

  7. 面试官:Redis的共享对象池了解吗?

    我正在面试间里焦急地等待着,突然听到了门外的脚步声,随即门被打开,穿着干净满脸清秀的青年走了进来,一股男士香水的淡香扑面而来. 面试官:"平时在工作中用过Redis吗?" 我:&q ...

  8. Redis学习笔记(二)-key相关命令【转载】

    转自 Redis学习笔记(二)-key相关命令 - 点解 - 博客园http://www.cnblogs.com/leny/p/5638764.html Redis支持的各种数据类型包括string, ...

  9. redis 序列化存入对象

    redis 序列化存入对象 //序列化 public static byte [] serialize(Object obj){ ObjectOutputStream obi=null; ByteAr ...

  10. redis 存储java对象 两种方式

    根据redis的存储原理,Redis的key和value都支持二进制安全的字符串 1.利用序列化和反序列化的方式存储java对象我们可以通过对象的序列化与反序列化完成存储于取出,这样就可以使用redi ...

随机推荐

  1. Web框架的应用

    从今天开始,我们将要学习有关Web框架的一些内容,在学习之前先来学习一下http协议,即基于http是如何通信的. http 概要:http是基于tcp/ip通信协议来传输数据的. 优点: 1.简单快 ...

  2. Linux常用操作2

    第1章 find命令扩展 转自:https://www.cnblogs.com/clsn/p/7520333.html 1.1 方法一 |xargs 通过|xargs将前面命令的执行结果传给后面. [ ...

  3. jquery中的置顶,置底,向上,向下的排序功能

    css .selectedLi{background: #f0ad4e;color:#fff;} html部分 <ul class="seetSelect2" id='sys ...

  4. 【Mood-13】Android --如何从初级工程师进化为高级工程师

    一  明确自我定位 现在你是初级工程师,但是你想当个高级工程师,所 以,你就要给自己定个目标,即:我是要成为高级工程师的男人.有了这个定位,并且努力朝着这个目标去努力,然后内心深处就会有一个感觉,这个 ...

  5. [AngularJS] $location 服务简介

    参考博客:  https://www.cnblogs.com/gaoruixin/p/6070502.html 简介 $location服务解析在浏览器地址栏中的URL(基于window.locati ...

  6. static int a

    static int a只被本文件可见,外部文件不可见;而int a如果在外部文件作以下声明: extern int a,那么它在声明的文件里也是可见的 详见:http://bbs.csdn.net/ ...

  7. SHOW SLAVE STATUS 详解

    MySQL同步功能由3个线程(master上1个,slave上2个)来实现.执行 DE>START SLAVEDE> 语句后,slave就创建一个I/O线程.I/O线程连接到master上 ...

  8. hdu-1166 敌兵布阵---树状数组模板

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1166 题目大意: 维护动态的区间和,单点更新,就是模板题 #include<iostream& ...

  9. 黑幕背后的Autorelease

    http://blog.sunnyxx.com/2014/10/15/behind-autorelease/ 我是前言 Autorelease机制是iOS开发者管理对象内存的好伙伴,MRC中,调用[o ...

  10. 动态规划专题(一)——状压DP

    前言 最近,决定好好恶补一下我最不擅长的\(DP\). 动态规划的种类还是很多的,我就从 状压\(DP\) 开始讲起吧. 简介 状压\(DP\)应该是一个比较玄学的东西. 由于它的时间复杂度是指数级的 ...