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那样更新同 ...
随机推荐
- JavaScript基础13——面向对象
什么是面向对象? 面向对象(Object Oriented,OO)是软件开发方法.面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统,交互式界面,应用结构,应用平台,分布式系统,网络管 ...
- 用OKR让你的员工嗨起来
在<OKR工作法>这本书中,作者主要用了叙述故事的方式来讲解了在OKR实践的整个过程,这样的讲解方式让整本书显得生动有趣,却又处处是引人深思的道理.比如说TeaBee在第一次OKR实践失败 ...
- 【数论】[因数个数]P4167樱花
题目描述 求不定方程 \(\frac {1}{x} + \frac{1}{y} = \frac{1}{n!}\)的正整数解的个数 \(n \leq 100^6\) Solution 化简得 \(x * ...
- 树形DP入门题目推荐以及解析
关于树形DP几道入门题目 今天恶补树形DP,感觉海星. 其实挺简单的. 介绍几道例题,我会的. 1.洛谷P1352 没有上司的舞会 我的一篇题解 我们可以考虑每一个节点都是有两种情况. 一个是被邀请: ...
- nuxtjs在vue组件中使用window对象编译报错的解决方法
我们知道nuxtjs是做服务端渲染的,他有很多声明周期是运行在服务端的,以及正常的vue声明周期mounted之前均是在服务端运行的,那么服务端是没有比如window对象的location.navag ...
- WAMP配置允许外网访问、绑定域名
如果wamp默认端口已经被占用,需要修改,则打开apache目录下的,conf文件下的httpd.conf文件 如图,把框中的默认80端口修改为自己需要的端口,然后重启WAMP即可. 想要实现外网访问 ...
- maven 左边空了
看一下maven: 解决方法:进入maven的配置,把maven的路径配置一下,就好了: 结果:
- 《JAVA程序设计》_第十一周学习总结
一.学习内容 13.1 URL类 URL类是java.net包中的一个重要的类,URL的实例封装着一个统一资源定位符,使用URL创建对象的应用程序称作客户端程序. 一个URL对象包含的三个基本信息:协 ...
- 第08组 Alpha冲刺(3/4)
队名 八组评分了吗 组长博客 小李的博客 作业博客 作业链接 组员1李昕晖(组长) 过去两天完成了哪些任务 文字/口头描述 11月19日了解各个小组的进度与难以攻破的地方,晚上安排开会,安排新的冲刺任 ...
- ubuntu下Vim安装失败
sudo apt-get install vim Reading package lists... Done Building dependency tree Reading state inform ...