redis底层实现的几种数据结构
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底层实现的几种数据结构的更多相关文章
- Redis(1)---五种数据结构
五种数据结构 一.全局key操作 --删 flushdb --清空当前选择的数据库 del mykey mykey2 --删除了两个 Keys --改 --将当前数据库中的 mysetkey 键移入到 ...
- 【Redis的那些事 · 上篇】Redis的介绍、五种数据结构演示和分布式锁
Redis是什么 Redis,全称是Remote Dictionary Service,翻译过来就是,远程字典服务. redis属于nosql非关系型数据库.Nosql常见的数据关系,基本上是以key ...
- Redis中5种数据结构的使用场景介绍
转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/108.html?1455861435 一.redis 数据结构使用场景 原 ...
- redis中5种数据结构的使用
一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 ...
- Redis中5种数据结构的使用场景
一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 ...
- Redis 中 5 种数据结构的使用场景介绍
这篇文章主要介绍了Redis中5种数据结构的使用场景介绍,本文对Redis中的5种数据类型String.Hash.List.Set.Sorted Set做了讲解,需要的朋友可以参考下 一.redis ...
- redis的5种数据结构的使用场景介绍
一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 ...
- Redis学习笔记之Redis中5种数据结构的使用场景介绍
原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 redis 中一共有5种数据结构 ...
- Redis 5种数据结构使用及注意事项
1优缺点 非常非常的快,有测评说比Memcached还快(当大家都是单CPU的时候),而且是无短板的快,读写都一般的快,所有API都差不多快,也没有MySQL Cluster.MongoDB那样更新同 ...
随机推荐
- PHP的简单了解
PHP 标识符/关键字/数据类型 标识符:其实标识符就是变量的名称,函数与类的名称也是标识符.PHP中标识符的规则: 1.标识符可以是任意长度的字母.数字.下划线,且不得以数字开头. 2.PHP中标识 ...
- MongoDB shell 1 数据库方法
方法名 描述 db.cloneDatabase() 从指定主机上克隆数据库 db.currentOp() 显示当前正在进行的操作 db.commandHelp() 返回数据库命令的帮助信息 db.cr ...
- vim文本编辑器——删除、复制、剪切、更改某一个字符、替换、撤销、关键字搜索
1.删除: (1)删除光标所在处的字符: 如上图所示:点击一次x键只能删除一个字符. (2)删除光标所在处后的n个字符(nx): 删除前: 输入6x: (3)删除光标所在的行(dd): 删除前: 输入 ...
- [Android] Android studio gradle 插件的版本号和 gradle 的版本号 的对应关系
[Android] Android studio gradle 插件的版本号和 gradle 的版本号 的对应关系 本博客地址: wukong1688 本文原文地址:https://www.cnblo ...
- 小胖的奇偶(Viojs1112)题解
原题: 题目描述 huyichen和xuzhenyi在玩一个游戏:他写一个由0和1组成的序列. huyichen选其中的一段(比如第3位到第5位),问他这段里面有奇数个1 还是偶数个1.xuzheny ...
- 洛谷 P1825 【[USACO11OPEN]玉米田迷宫Corn Maze】
P1825 传送门 简单的题意 就是一个有传送门的迷宫问题(我一开始以为是只有1个传送门,然后我就凉了). 大体思路 先把传送门先存起来,然后跑一下\(BFS\). 然后,就做完了. 代码鸭 #inc ...
- 【数论】[逆元,错排]P4071排列计数
题目描述 求有多少种长度为n的系列A,满足以下条件: 1~n这n个数在序列中各出现一次:若第i个数a[i]的值为i,则称i是稳定的.序列恰有m个数是稳定的. 输出序列个数对1e9+7取模的结果. So ...
- 【luoguP4720】【模板】扩展卢卡斯
快速阶乘与(扩展)卢卡斯定理 \(p\)为质数时 考虑 \(n!~mod~p\) 的性质 当\(n>>p\)时,不妨将\(n!\)中的因子\(p\)提出来 \(n!\) 可以写成 \(a* ...
- MySQL 是如何利用索引的
阅读本文大概需要 4 分钟. 一.前言 在 MySQL 中进行 SQL 优化的时候,经常会在一些情况下,对 MySQL 能否利用索引有一些迷惑.例如: MySQL 在遇到范围查询条件的时候就停止匹配了 ...
- win10安装ubuntu双系统遇到的问题
安装过程学习了几个博客 Ubuntu 16.04与Win10双系统双硬盘安装图解:https://www.cnblogs.com/coxiseed/p/9945202.html?tdsourcetag ...