我们继续接着上一篇博客,今天来看看内存映射数据结构。

  上篇我们讲了内部数据结构,虽然内部数据结构非常强大,但是创建一系列完整的数据结构本身也是一件相当耗费时间的工作,当一个对象包含的元素数量并不多,或者元素本身的体积并不大时,使用代价高昂的内部数据结构并不是最好的办法。因此我们会用内存映射数据结构来代替内部数据结构。

  内存映射数据结构是一系列经过特殊编码的字节序列,创建他们所消耗的内存通常比作用类似的内部数据结构要少得多,如果使用得当,内存映射数据结构可以为用户节省大量的内存。不过,内存映射数据结构的编码和操作方式要比内部数据结构复杂的多,所以内存映射数据结构所占用的CPU时间会比作用类似的内部结构要多。

2.1整数集合

  整数集合(intset)用于有序、无重复地保存多个整数值,他会根据元素的值,自动选择该用什么长度的整数类型来保存元素。

  2.1.1 整数集合的应用

  intset是集合键的底层实现之一,如果一个集合满足:

  * 值保存着整数元素;

  * 元素的数量不多;

  那么就会使用intset来保存集合元素。

  2.1.2 数据结构和主要操作

typedef struct intset {
// 保存元素所使用的类型的长度
uint32_t encoding;
// 元素个数
uint32_t length;
// 保存元素的数组
int8_t contents[];
} intset; 

  encoding 的值可以是以下三个常量的其中一个(定义位于intset.c ):
    #define INTSET_ENC_INT16 (sizeof(int16_t))
    #define INTSET_ENC_INT32 (sizeof(int32_t))
    #define INTSET_ENC_INT64 (sizeof(int64_t))

  contents数组是实际保存元素的地方,数组有一下两个特性:

  * 没有重复元素;

  * 从小到大排序;

  contents的 int8_t类型只是作为一个占位符使用,intset不使用int8_t类型保存任何元素。新增元素默认的encoding是int16_t,当添加的新元素不适合于当前intset的编码类型时,intset集合将会进行升级。

  2.1.3 小结

  * intset用于有序、无重复的保存多个整数值。他会根据元素的值,自动选择该用什么长度的整数类型来保存元素;

  * 当一个位长度更长的整数值添加到intset时,需要对intset进行升级,新intset中的每个元素的位长度都等于新添加值的位长度,但原有元素的值不变;

  * 升级会引起整个intset进行内存重分配,并移动集合中的所有元素,这个操作的复杂度为O(N);

  * intset只支持升级,不支持降级;

  * intset是有序的,程序使用二分法查找算法来实现查找操作,复杂度为O(lgN);

2.2 压缩列表

  ziplist 是由一系列特殊编码的内存块构成的列表,一个ziplist 可以包含多个节点(entry),每个节点可以保存一个长度受限的字符数组(不以\0结尾的char数组)或者整数,包括:  

  • 字符数组
  – 长度小于等于63 (26 - 1)字节的字符数组
  – 长度小于等于16383 (214 - 1)字节的字符数组
  – 长度小于等于4294967295 (232 - 1)字节的字符数组
  • 整数
  – 4 位长,介于0 至12 之间的无符号整数
  – 1 字节长,有符号整数
  – 3 字节长,有符号整数
  – int16_t 类型整数
  – int32_t 类型整数
  – int64_t 类型整数

  因为ziplist节约内存的性质,它被哈希键、列表建和有序集合键作为初始化的底层实现来使用。

  2.2.1 ziplist的结构:

  

  因为ziplist header 部分的长度总是固定的(4 字节+ 4 字节+ 2 字节),因此将指针移动到表头节点的复杂度为常数时间;除此之外,因为表尾节点的地址可以通过zltail 计算得出,因此将指针移动到表尾节点的复杂度也为常数时间。  

  因为ziplist 由连续的内存块构成,在最坏情况下,当ziplistPush 、ziplistDelete 这类对节点进行增加或删除的函数之后,程序需要执行一种称为连锁更新的动作来维持ziplist 结构本身的性质,所以这些函数的最坏复杂度都为O(N2) 。不过,因为这种最坏情况出现的概率并不高,所以大可以放心使用ziplist ,而不必太担心出现最坏情况。

  2.2.2 节点的构成:

  

  pre_entry_length:记录了前一个节点的长度,通过这个值,可以进行指针计算,从而跳转到上一个节点。(注:若前一个节点的长度小于254字节,则使用一个字节保存pre_entry_length的值,若大于等于254,则使用5个字节保存,其中第一个字节保存254,后4个字节保存前一个节点的实际长度);

  encoding:记录了content的数据类型,长度为2个bit,它的值可以是00、01、10和11(其中00、01和10表示Content中保存着字符数组;11表示content中保存着整数);

  length:记录了content的数据长度;

  content:保存着节点的内容

  2.2.3 小结:

  * ziplist是由一系列特殊编码的内存块构成的列表,它可以保存字符数组或整数值,它还是哈希键、列表键和有序集合键的底层实现之一。

  * 添加和删除ziplist节点有可能会引起连锁更新,因此,添加和删除操作的最坏复杂度为O(N2),不过,因为连锁更新的出现概率并不高,所以一般可以将添加和删除操作的复杂度视为O(N)。

redis底层设计(二)——内存映射数据结构的更多相关文章

  1. redisbook笔记——redis内存映射数据结构

    虽然内部数据结构非常强大,但是创建一系列完整的数据结构本身也是一件相当耗费内存的工作,当一个对象包含的元素数量并不多,或者元素本身的体积并不大时,使用代价高昂的内部数据结构并不是最好的办法. 为了解决 ...

  2. redis学习笔记——内存映射数据结构

    内存映射数据结构 解决问题:当一个对象包含的元素数量并不多,或者元素本身的体积并不大时,使用代价高昂的内部数据结构并不是最好的办法. 内存映射数据结构是一系列经过特殊编码的字节序列,创建它们所消耗的内 ...

  3. redis底层设计(一)——内部数据结构

    redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set ...

  4. redis底层实现的几种数据结构

    redis底层数据结构 一.简单动态字符串(SDS) 定义: struct sdshdr{ int len;    //SDS所保存的字符串长度 int free //记录buf数组中为使用的字节数量 ...

  5. redis底层设计(三)——redis数据类型

    今天我们来看一下redis的数据类型.既然redis的键值对可以保存不同类型的值,那么很自然就需要对键值对的类型进行检查以及多态处理.下面我们将对redis所使用的对象系统进行了解,并分别观察字符串. ...

  6. redis底层设计(五)——内部运作机制

    5.1 数据库 5.1.1 数据库的结构: Redis 中的每个数据库,都由一个redis.h/redisDb 结构表示: typedef struct redisDb { // 保存着数据库以整数表 ...

  7. redis底层设计(四)——功能的实现

    redis中我们会经常用到事务.订阅与发布.Lua脚本以及慢查询日志,接下来我们就一一对他们进行探讨学习. 4.1事务 redis通过MULTI.DISCARD.EXEC和WATCH四个命令来实现事务 ...

  8. 10w+QPS 的 Redis 真的只是因为单线程和内存?360° 深入底层设计为你揭开 Redis 神秘面纱!

    原文链接:10w+QPS 的 Redis 真的只是因为单线程和内存?360° 深入底层设计为你揭开 Redis 神秘面纱! 你以为 Redis 这么快仅仅因为单线程和基于内存? 那么你想得太少了,我个 ...

  9. Redis底层数据结构详解

    上一篇说了Redis有五种数据类型,今天就来聊一下Redis底层的数据结构是什么样的.是这一周看了<redis设计与实现>一书,现来总结一下.(看书总是非常烦躁的!) Redis是由C语言 ...

随机推荐

  1. ionic提示弹框

    //提示框 .factory('TipsPort', function ($ionicPopup) { var TipsPort = function (tipsText, SureFunction, ...

  2. 有字库API方法使用一二

    有字库是全球第一中文web font(在线字体)服务平台,官网写的有点牛X 需要注册帐号才可以使用,免费字体有限制,一页字数不可以超过500 个,超过500个字体就不会为标签添加字体样式,比较坑爹! ...

  3. mac 苹果多版本jdk自由切换

    场景 手头上的工具有时候依赖低版本jdk,有时候需要高版本jdk, 如何在不同版本jdk之间来回自由的切换? 安装 首选需要去官网下载dmg安装包,地址:https://www.oracle.com/ ...

  4. Django 系统日志logging

    Django使用Python内建的logging模块去建造自己的系统日志的,如果你想详细了解这个模块的话,请自己去看python的说明文档,这里仅仅介绍Django中的日志系统. 日志配置包括四个部分 ...

  5. Python操作字典(dict)

    一.字典定义 >>> dict={} 二.字典元素添加 >>> dict['性别']='男' >>> dict {'性别': '男'} >& ...

  6. EF CodeFirst使用Nuget更新数据库

    常用命令: 1.开启迁移 Enable-Migrations -EnableAutomaticMigrations 2.添加一条迁移记录 Add-Migration AddMigration001 3 ...

  7. 记一次 MySQL semaphore crash 的分析(爱可生)

    文章来源:爱可生云数据库作者:洪斌 DBA应该对InnoDB: Semaphore wait has lasted > 600 seconds. We intentionally crash t ...

  8. Kernel数据结构移植(list和rbtree)

    主要移植了内核中的 list,rbtree.使得这2个数据结构在用户态程序中也能使用. 同时用 cpputest 对移植后的代码进行了测试.(测试代码其实也是使用这2个数据结构的方法) 内核代码的如下 ...

  9. March 07th, 2018 Week 10th Wednesday

    Better later than never. 亡羊补牢,时犹未晚. Time and again all of us are told to complete the tasks assigned ...

  10. 英语初级学习系列-00-Hello-打招呼

    开场白 当二咪过生日的时候,作为主人公(host)该如何开场呢? 1. Good evening, ladies and gentlemen. 先生们女士们,晚上好. 2. Welcome to my ...