redis底层数据结构

一、简单动态字符串(SDS)

定义:

struct sdshdr{

  int len;    //SDS所保存的字符串长度

  int free //记录buf数组中为使用的字节数量,预留内存长度

  char buf[] //字节数组,用于保存字符串

}

SDS与C字符串的区别及特点:

1)获取字符串长度:

  C字符串:遍历整个字符串,直至遇到结束符为止,复杂度为O(n)。

  SDS:在len中记录了本身的长度,所以获取一个SDS长度的复杂度为O(1)。

2)杜绝缓存区溢出

  C字符串:不记录本身的长度,当将一个字符串拼接到另一个字符串的末尾时,如果内存不够多的话,就会产生缓存区的溢出。

  SDS:在进行拼接之前,会先检查给定的SDS空间是否足够,如果不够,会先扩展SDS的空间,然后才执行拼接操作。

3)减少内存重新分配次数

  SDS:通过空间预分配额外空间作为保留使用,从而减少内存的重分配为题。

  额外分配空间数量的公式:

    1、当SDS的长度小于1MB的时候,分配与len属性的值相同长度的内存作为预存。(实际上所有的长度将多一字节保存空字符)

    2、当SDS的长度大于等于1MB的时候,那么程序会分配1MB的未使用空间。

4)惰性空间释放

  SDS的API需要缩短SDS保存的字符串时,程序不会立即使用内存重分配来回收缩短后多出来的字节,

  而是使用free属性将这些字节的数量记录起来,并等待将来使用。

5)二进制安全

  C字符串:必须符合某种编码。并且除了字符串末尾之外,字符串里面并不能包含空字符,所以导致其职能保存文本数据,

  而不能保存像图片、音频、视频、压缩文件这样的二进制文件。

  SDS:所有SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数据,程序不会对其中的数据做任何限制、过滤、或者假设。

  这主要得益于其使用len属性值而不是使用空字符来判断字符串是否结束。所以SDS中的buf属性成为字节数组而不是字符数组。

6)兼容部分C字符串函数

  

二、链表

链表节点定义:

typedef struct listNode{

  struct listNode *prev;//前置节点

  struct listNode *next;  //后置节点

  void *value;     //节点值

}listNode;

链表定义

typedef struct list{

  listNode *head;  //表头节点

  listNode *tail;   //表尾节点

  unsigned long len;  //链表包含的节点数量

  void *(*dup)(void *ptr);  //节点值复制函数

  void (*free)(void *ptr);  //节点值释放函数

  int (*match)(void *ptr,void *key);  //节点值对比函数

}list;

链表实现特性总结:

1、双端:链表节点带有prev和next指针。

2、无环:表头节点的prev和表尾节点的next都指向null。

3、带表头指针和表尾指针:通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为O(1)

4、带链表长度计数器:获取长度直接通过len获得,复杂度为O(1)。

5、多态:使用void* 保存节点值,所以可以使用来保存各种不同类型的值。

三、字典

1)哈希表定义

typedef struct dictht{

  dictEntry **table;      //哈希表数组

  unsigned long size;    //哈希表大小

  unsihned long sizemask;  //哈希表大小掩码

  unsigned long used;    //哈希表已有节点的数量。

}dictht;

2)哈希表节点定义(table锁指向)

typedef struct dictEntry{

  void *key;  //键

  union{  //值

    void *val;

    uint64 _tu64;

    int64 _ts64;

  }v;

  stuct dictEntry *next;  //指向下一个哈希表节点

}dictEntry;

3)字典定义

typedef struct dict{

  dictType *type;  //类型特定函数

  void *privdata;  //私有数据

  dictht ht[2];    //哈希表

  int trehashidx;        //rehash索引,当rehash不在进行时,值为-1

}dict;

type:指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数。

privdata:保存需要传给那些类型特定函数的可选参数

ht[2]:是一个包含两个项的数组,每个项都是一个哈希表,一般只用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。

rehashidx:记录rehash目前的进度,如果目前没有进行rehash,那么它的值为-1。

4)dictType定义(type所指向)

typedef struct dictType{

  unsigned int (*hashFunction)(const void *key);     //计算哈希值的函数

  void *(*keyDup)(void *privdata,const void *key)  //复制键的函数

  void *(*avlDp)(void *privdata,const void *obj);  //复制值的函数

  int (*ketCompare)(void privdata, const void *key1,const void *key2);  //对比键的函数

  void (*keyDestructor)(void *privdata,void *key);  //销毁键的函数

  void (*valDestrctor)(void *privdata,void *obj)  ;    //销毁值的函数  

}dictType;

5)哈希算法

  将新的键值对添加到字典里面。程序先根据键值对的键,计算出哈希值和索引值,然后在根据索引值,将包含

新键值对的哈希表节点放到哈希表数组指定索引上面。

  1、使用字典设置的哈希函数。计算出键key的哈希值

  2、使用哈希表的sizemask属性和哈希值,计算出索引值

  3、根据情况存储在ht[0]或ht[1]上。

6)解决键冲突

  当有两个或者以上数量的键呗分配到了哈希表数组的同意个索引上面时,则会发生冲突,redis的哈希表使用链地址的方法解决冲突,

每个哈希表节点有一个next指针,多个哈希表节点可以使用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个

单向链表连接起来,从而解决键冲突的问题。

7)rehash

  当哈希表保存的键值对增长或减少到一定的数量时,为了让哈希表的负载因子维持在一个合理的范围之内,程序需对哈希表进行相应的扩展或者收缩。

  rehash可以完成扩展和收缩功能,其步骤为

    1、为ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当期那包含的键值对数量。

      如果是扩展,ht[1]的大小为第一个大于ht[0].used*2的2^n(2的n次方幂)

    2、将ht[0]的所有键值对rehash到ht[1]上面,rehash指的是重新计算键的哈希值和索引值,然后放到对应的ht[1]哈希表的指定位置上。

    3、ht[0]所有数据都迁移后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]新建一个空白哈希表,为下一次rehash做准备。

8)渐进式rehash

    考虑到大量数据迁移导致CPU繁忙,采用渐进式rehash方案

      1、为ht[1]分配空间,同时持有两个哈希表(一个空表,一个有数据)

      2、维持一个技术器rehashidx,初始值为0

      3、每次对字典增删查改,会顺带将ht[0]中的数据迁移到ht[1],rehashidx++。(ht[0]中的数据只减不增)

      4、知道rehash操作完成,rehashidx值设为-1

采用分而治之的思想,将庞大的迁移工作量划分为每一次SURD中,避免服务繁忙。

四、跳跃表

跳跃表定义

typedef struct zskiplist

{

  struct zskiplistNode *header,*tail;    //表头节点和表尾节点

  unsigned long length;        //表中节点的数量

  int level;              //表中层数最大的节点的层数

}zskiplist

zskiplist结构:

  header:指向跳跃表的表头节点

  tail:指向跳跃表的表尾节点

  level:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)

  length:记录跳跃表的长度,也是跳跃表目前包含节点的数量

跳跃表节点定义

typedef struct zskiplistNode{

  struct zskiplistLevel{//层

    struct zskiplistNode*forward;  //前进指针

    unsigned int span;      //跨度

  }level[]      

  struct zskiplistNode*backward;    //后退指针

  double score;            //分值

  robj *obj;              //成员对象

;}zskiplistNode;

解析:

  层:跳跃表节点的level数组可以包含多个元素,每个元素包含一个指向其他节点的指针。一般来说,层数越多,访问其他节点的速度就越快。

五、整数集合

整数集合的定义

typedef struct  intset{

  uint32_t encoding;    //编码方式

  uint32_t length;      //结合包含的元素数量

  int8_t contents[];      //保存元素的数组

}intset;

encoding的编码决定contents数组的值类型。

升级

何时升级:新添加元素的类型比目前整数集合里所有的元素类型都要长时需要升级。

怎么升:1、扩展整数集合底层数组的空间大小

    2、将现有元素都转换与新元素相同的类型,并将转换后的元素按顺序放到新的位置上

    3、将新元素添加到底层数组里

升级的好处:1、提升整数集合的灵活性

      2、尽可能的节约内存

能否降级? 不能!

六、压缩列表

压缩列表是列表键和哈希键的底层实现之一。

压缩列表的构成

引用redis设计与实现

redis底层实现的几种数据结构的更多相关文章

  1. Redis(1)---五种数据结构

    五种数据结构 一.全局key操作 --删 flushdb --清空当前选择的数据库 del mykey mykey2 --删除了两个 Keys --改 --将当前数据库中的 mysetkey 键移入到 ...

  2. 【Redis的那些事 · 上篇】Redis的介绍、五种数据结构演示和分布式锁

    Redis是什么 Redis,全称是Remote Dictionary Service,翻译过来就是,远程字典服务. redis属于nosql非关系型数据库.Nosql常见的数据关系,基本上是以key ...

  3. Redis中5种数据结构的使用场景介绍

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/108.html?1455861435 一.redis 数据结构使用场景 原 ...

  4. redis中5种数据结构的使用

    一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 ...

  5. Redis中5种数据结构的使用场景

    一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 ...

  6. Redis 中 5 种数据结构的使用场景介绍

    这篇文章主要介绍了Redis中5种数据结构的使用场景介绍,本文对Redis中的5种数据类型String.Hash.List.Set.Sorted Set做了讲解,需要的朋友可以参考下 一.redis ...

  7. redis的5种数据结构的使用场景介绍

    一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 ...

  8. Redis学习笔记之Redis中5种数据结构的使用场景介绍

    原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 redis 中一共有5种数据结构 ...

  9. Redis 5种数据结构使用及注意事项

    1优缺点 非常非常的快,有测评说比Memcached还快(当大家都是单CPU的时候),而且是无短板的快,读写都一般的快,所有API都差不多快,也没有MySQL Cluster.MongoDB那样更新同 ...

随机推荐

  1. vue的认识===下载

    VUE:不建议直接操作DOM Vue.js是前端三大新框架:Angular.js.React.js.Vue.js之一,Vue.js目前的使用和关注程度在三大框架中稍微 胜出,并且它的热度还在递增 Vu ...

  2. Numpy | 01 简介

    NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库. NumPy 是一个运行速度非常快的数学库 ...

  3. [PHP]Laravel无法使用COOKIE和SESSION的解决方法

    COOKIE和SESSION的具体使用百度和官方文档上都有. 但是,文档里没有说明必须经过相应的中间件才能使用,百度搜索结果都是彼此copy的bullshit!!! 其实最终解决办法很简单,完全不是网 ...

  4. js实现延迟加载

    defer async.await 动态创建DOM jQ的getScript()方法 window.onload().$(document).ready() Promise setTimeout.se ...

  5. 正睿2019省选附加赛 Day10 (这篇其实已经都咕咕了...)

    目录 2019.3.13 A.算算算(二项式定理 斯特林数) B.买买买 C.树树树 2019.3.13 比赛链接 A.算算算(二项式定理 斯特林数) 题目链接 \(x^k\)可以用二项式定理展开,需 ...

  6. nginx 访问控制之 http_referer

    在rewrite时,曾经用过该变量,当时实现了防盗链功能. 其实基于该变量,我们也可以做一些特殊的需求. 示例: 背景:网站被黑挂马,搜索引擎收录的网页是有问题的,当通过搜索引擎点击到网站时,却显示一 ...

  7. 数据结构(一)二叉树 & avl树 & 红黑树 & B-树 & B+树 & B*树 & R树

    参考文档: avl树:http://lib.csdn.net/article/datastructure/9204 avl树:http://blog.csdn.net/javazejian/artic ...

  8. 【转】Android原生PDF功能实现

    1.背景 近期,公司希望实现安卓原生端的PDF功能,要求:高效.实用. 经过两天的调研.编码,实现了一个简单Demo,如上图所示.关于安卓原生端的PDF功能实现,技术点还是很多的,为了咱们安卓开发的同 ...

  9. Anaconda更新失败简单解决[CondaHTTPError: HTTP 000 CONNECTION FAILED for url]

    问题:conda无法安装更新,报错内容如下:参考链接:conda httperror http none none for url none Anaconda更新失败 conda create -n ...

  10. Android固件img文件的解包, 修改和打包的命令行操作

    Android固件img文件的解包打包 To Unpack-Modify-Pach the system.img, I have followed the following procedure: a ...