Redis基础数据结构-基于2.8
SDS
SDS是Redis中String的底层数据结构,数据结构如下,SDS保留了传统的C字符串表达方式即数组的最后一个元素是'/0'结尾。此外还添加了两个字段len和free,其中len表示字符串长度,free代表空闲空间。
class sds {
int len;
int free;
char[] buf;
}
那么这两个添加的元素有什么作用呢?
- 常数复杂度获取字符长度。首先第一点就是C数组是不记录长度的,那么为了获取字符串的长度每次就得遍历数组内的全部元素,这无疑会增加时间损耗,有了len就可以实时记录数组长度,获取字符串长度的时间复杂度由O(n)变成了O(1)。
- 杜绝缓冲溢出。我们知道传统的C数组是不记录本身长度的,默认会认为你为要纳入数组的元素分配了足够的内存,如果不对边界进行校验的话可能就会发生缓冲区溢出的情况,举个例子,比如两个字符串数组S1,S2长度都为6,
S1 = [ 'H','e', 'l', 'l', 'o', '/0'],S2 = [ 'W','o', 'r, 'l', 'd, '/0']两个数组刚好在一段连续的内存中即长度为12的内存中,如果S1数组的index = 6添加字符'w',由于S1长度不足7,那么可能就会溢出到S2的index = 0的内存里。而SDS添加元素的API里通过对free的校验若内存不足则进行扩展,则完全避免了这种缓冲溢出的情况。 - 减少内存分配的次数。上面提到如果free的空间不满足添加元素长度的需求则需要对SDS的buf进行扩容。此外还有减少元素时回收内存避免内存泄漏。Redis对性能要求是很高的,尤其是内存数据修改更是频繁,频繁的更改和扩容会对性能产生巨大影响,因此Redis使用两种策略来避免这种情况。第一是空间预分配,空间预分配会在buf长度小于1MB时分配和len相等的free长度,大于1MB时固定额外分配1MB的空间。第二是惰性空间释放,buf缩容时free会增加但不会实际回收对应内存,以便后续使用。
- 二进制安全。C数组中除了字符串的末尾之外是不能记录空字符的,否则会被认为是字符串的结尾。而SDS由于len等有标识字符长度所以是二进制安全的可以用来保存各种数据。
- 兼容C函数API。由于SDS的本质还是
buf这个字符数组,所以本质上可以使用如strcmp等C函数。
LinkedList(链表)
典型的双端链表结构,无环,带有链表计数器,支持多态
// 节点数据结构
class ListNode {
ListNode prev;
ListNode next;
int value;
}
class List {
ListNode head;
ListNode tail;
long len;
// 赋值连表保存的值
void dup();
// 比较连表的值和某个输入值
int match();
// 释放连表保存的值
void free();
}
Dict(字典)
数据结构:字典是Redis最基础的结构,实现和Java的HashMap类似。即拉链法解决hash冲突的方式。数据结构如下。
// 键值对数据结构
class DictEntry<K, V> {
K key;
V value;
// 拉链法解决hash冲突,一个key一个bucket,相同buket路由到同一个链表
dictEntry next;
}
// hash表数据结构
class Dicht{
DictEntry[] table;
// hash表大小,即table数组大小
int size;
// 掩码,用于计算索引值,总是等于size减1
int sizeMask = size - 1;
// 键值对数量
long used;
}
// dict字典数据结构
class Dict {
// ht相当于master加replicat,ht[1]只在reshash的时候使用
Dict ht[2];
// -1 不在rehash,>= 0 rehash中
int rehashidx;
}
哈希冲突:当一个元素加入dict时,会先对hash函数计算对象的hash值,然后再和sizeMask作与运算,这里的与运算其实就是对size取余的操作(因为size为2n,sizeMask = size - 1,那么sizeMask低位都为1),以便将hash值放在指定范围的数组下标内。如果产生hash冲突会通过拉链法将相同hash值的数据通过链表链接从而放到数组的一个bucket下标内。
扩展与收缩:hash表中有一个loadFactor即负载因子,计算方式:loadFactor= ht[0].used / ht[0].size,其实就是hash表的使用率,为了维持负载因子在合理的范围内,就需要对hash表进行扩展和收缩,触发扩展的条件是:loadFactor > 1 && noBGSAVEOrBGREWRITEAOF(没有BgSave或BgRewriteAof) || loadFactor > 5,触发收缩的条件是loadFactor < 0.1实现扩展收缩的条件是rehash,rehash大致步骤如下
- 为
ht[1]分配空间,大小是第一个大于或等于ht[0].used*2的2的次幂。 - 将
ht[0]的元素rehash到ht[1],因为此处sizeMask变了,所以对应的与运算的值也会发生改变。 - 交换
ht[0]和ht[1],即释放ht[0]的空间,将ht[0]指向ht[1]。
渐进式rehash:如果hash表的元素很多,一次性rehash可能会造成性能问题甚至一段时间不可用。所以rehash一般是渐进式的,渐进式的rehash步骤如下
- 为
ht[1]分配空间,置rehashidx = 0 - 在元素进行添加、更新、查找、删除时会顺带将在索引为
rehashidx的元素从ht[0]rehash到ht[1]上。每个索引rehash完成后rehashidx++。 - 最终所有索引的键值对完成rehash时,交换
ht[0]和ht[1],置rehashidx = -1。
注意在渐进式rehash过程中,元素查找涉及两个hash表,会先查找ht[0]再对ht[1]进行查找
SkipList(跳跃表)
跳表是一种类似于树的层级结构,数据结构如下:
class SkipListNode{
// 当前节点在每一层的标识,maxSize = 32
Level[] level;
// 后退指针,即指向上一节点
SkipListNode backword;
// 当前节点值
Object value;
// value的score值,通过这个进行排序,从小到大
int score;
// 每层的数据结构
class Level {
// 当前层级对应的下一个节点
SkipListNode next;
// 当前层级两个节点跨度,通过这个可以快速在任意层级判断当前的元素位置
int span;
}
}
class SkipList {
// 头节点
SkipListNode head;
// 尾节点
SkipListNode tail;
// 跳表元素长度
int length;
// 节点中层级最大的值
int level;
}
跳表是一层一层向上选举节点,比如一组链表在Level[0]时是0->1->2->3->4->5->6->7->8],通过随机向上选举那么Level[1]的表示可能为0->3->6->8。同理可以继续晚上选举。通过这种方式从而达到O(logn)的查询时间复杂度。
IntSet(整数集合)
整数集合如其名记录一系列整数,是底层集合键的底层实现之一,数据结构如下
class IntSet{
// int_8,int_32,int_64
Object encoding;
// 保存整数集合的
int[] contents;
// 整数集合的长度
int length;
}
contents中的元素是有序且无重复的,当新加入的元素大于encoding所能支持的长度时,IntSet便会进行升级操作,即修改contents的分配内存策略,并重新索引其中的元素,需要注意的是并不支持降级操作。这种升级策略尽可能保证了数据结构的灵活性的同时节约了内存。
ZipList(压缩链表)
压缩链表是链表键和哈希键的底层实现之一。数据结构如下
// 压缩链表节点
class ZipListNode {
// value的类型及长度
int encoding;
// 实际值
Object contents;
// 前一节点长度,1byte(前一节点长度小于255)或者5byte(前一节点长度大于255)
Object previousEntryLength;
}
// 压缩链表
class ZipList{
// 压缩链表字节数,4bytes
int zlBytes;
// 压缩链表结尾距离起始节点的地址偏移量,4bytes
int zlTail;
// 节点数量 2bytes
short zlLen;
// 压缩链表元素,不定长
ZipListNode[] entries;
// 1byte 固定0Xff即255
byte zllend;
}
压缩链表在链表键或哈希键较短时作为底层实现,可以大大节省内存资源。每一个压缩链表由头部,节点,尾部三部分组成。其中头部由压缩链表字节数(zlBytes)、压缩链表尾部地址(zlTail)、元素数量(zlLen)组成。尾部是固定的数字0xff即标识EOF。
而在头部和尾部中间便是实际存储的元素节点。压缩链表的节点由previous Entry Length(前一节点长度,单位byte)、contents(元素值)、encoding(元素编码组成)。通过前一元素的长度q以及当前元素的起始地址 p 可以很容易前一元素的地址即p-q。因此能够容易的实现从表尾向表头遍历。encoding保存了contents的数据类型及长度。encoding的最高两位如果11且encoding长度为1byte代表记录的是整数值,其他情况保存的是字节数组(字节数组低位标识数组最大长度)。
连锁更新:由于节点的previousEntryLength记录了前一节点的长度,当前一节点长度发生变化时很有可能产生连锁反应,即连锁更新。比如当前元素长度本来是254字节,其中previousEntryLength是1字节。但如果前一元素的长度超过了255,则当前元素的previousEntryLength变成了5,当前元素的字节长度变成了258,这样当前元素的下一个元素也要变化,这样就发生了连锁更新。连锁更新会影响性能,但一定范围内是性能损耗是比较小的。
Redis基础数据结构-基于2.8的更多相关文章
- redis 基础数据结构实现
参考文献 redis数据结构分析 Skip List(跳跃表)原理详解 redis 源码分析之内存布局 Redis 基础数据结构与对象 Redis设计与实现-第7章-压缩列表 在redis中构建了自己 ...
- redis基础数据结构及编码方式
redis基础数据结构和编码方式 一.基础数据结构 1)简单动态字符串 2)双端链表 3)字典 4)跳跃表 5)整数集合 6)压缩列表 二.对象类型与编码 在redis的数据库中创建一个新的键值对时, ...
- 1.基础: 万丈高楼平地起——Redis基础数据结构 学习记录
<Redis深度历险:核心原理和应用实践>1.基础: 万丈高楼平地起——Redis基础数据结构 学习记录http://naotu.baidu.com/file/b874e2624d3f37 ...
- Redis——基础数据结构
Redis提供了5种基础数据结构,分别是String,list,set,hash和zset. 1.String Redis所有的键都是String.Redis的String是动态字符串,内部结构类似J ...
- Redis基础数据结构
Redis数据库中每个键值对都是由对象( c 的结构体对象)组成的. 数据库键总是一个字符串对象(string object) 数据库键的值可以使字符串对象.列表对象(list object).哈希对 ...
- Redis 基础数据结构之二 list(列表)
Redis 有 5 种基础数据结构,分别为:string (字符串).list (列表).set (集合).hash (哈希) 和 zset (有序集合). 今天来说一下list(列表)这种数据结构, ...
- Redis 基础数据结构之一:string(字符串)
Redis 有 5 种基础数据结构,分别为:string (字符串).list (列表).set (集合).hash (哈希) 和 zset (有序集合),Redis存储数据的结构是键值对形式的. 首 ...
- 浅析Redis基础数据结构
Redis是一种内存数据库,所以可以很方便的直接基于内存中的数据结构,对外提供众多的接口,而这些接口实际上就是对不同的数据结构进行操作的算法,首先redis本身是一种key-value的数据库,对于v ...
- Redis 基础数据结构与对象
Redis用到的底层数据结构有:简单动态字符串.双端链表.字典.压缩列表.整数集合.跳跃表等,Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包 ...
随机推荐
- WPF---样式(一)
一.概要 Style通俗的讲,就是一组Setter,设置目标控件的一些属性,便于复用. 注:如果一个属性在控件本身进行了设定,那么Style中的对应属性值会被覆盖掉. 二.命名样式和目标样式 命名样式 ...
- 【OpenLayers】入门教程地址
[OpenLayers]入门教程地址: 点击进入 http://anzhihun.coding.me/ol3-primer/index.html 简书地址 : http://www.jians ...
- 如何打一个RPM包
如何打一个RPM包 参考链接:RPM打包原理.示例.详解及备查 前言 本文只是一个RPM安装的例子,并没有对RPM做比较详尽的叙述,更为详尽的讲解,可以在上面的链接中找到. RPM是啥? RPM(Re ...
- vue 元素拖动效果
<draggable v-model="preface" chosenClass="chosen" ghost-class="ghost&quo ...
- spring整合jdbc方法一
用了一段时间的spring这,闲来没事做一下spring整合jdbc 目录文件 导入jar包 由于spring的jar包是在myeclipse中自动导入的有些暂时用不到的也没有处理. Emp类 pac ...
- WEB漏洞——CSRF、SSRF
CSRF漏洞 CSRF( Cross- site request forgery,跨站请求伪造)也被称为 One Click Attack或者 Session Riding,通常缩写为CSRF或者XS ...
- KMP算法中的几个疑问
KMP算法next数组求解实现 首先我们通过应用场景将KMP算法中用到的名词做一个说明: 在一个字符串(string1)中查询是否存在另一个字符串(string2). 在字符串匹配算法中,我们通常将字 ...
- 菜狗、《灵笼》、《时光代理人》,重新审视Z世代的电商逻辑
来源:懂懂笔记 B站还有多少潜力可以挖掘? 虽然B站的最新财报依然还是亏损,但同时也让人看到更多的可能性. 从财报数据的亮点来看,一是营收增长,B站二季度营收为44.95亿元,同比增长72%.营收上B ...
- JD 评论晒图爬虫
JD 评论晒图爬虫 #coding=utf-8 import requests import re import os __author__ = 'depy' """ j ...
- 【第二十篇】-Maven IntelliJ之Spring Cloud直播商城 b2b2c电子商务技术总结
Maven IntelliJ IntelliJ IDEA 已经内建了对 Maven 的支持.我们在此例中使用的是 IntelliJ IDEA 社区版 11.1. IntelliJ IDEA 的一些特性 ...