Redis中的基本数据结构
Redis基础数据结构
基础数据结构
sds简单动态字符串
数据结构
typedef struct sdstr{
int len // 字符串分配的字节
int free // 未使用的字节数
char buff[] // 存储字符串的数组
}
sds是字符串对象的底层实现之一
sds的特性
赋值操作会统计字符串的长度然后将字符串存入buff里面,同时设定长度和使用的长度
例如 "hello"这个字符串的存储结构如下
{
len:5,
free:0,
buff:['h','e','l','l','o','\0']
}
修改的时候会比较麻烦,分为两种情况
一是由段字符串变长:例如:由"hello"变为"hello,redis".
这个时候系统会检查原本的sds字符串是否有空余空间,剩余空间为0,会分配等同于修改后字符串长度的剩余空间给sds,这个时候字符串的free属性会变为11,然后执行sdscat(),这个时候buff会变为['h','e','l','l','o',',','r','e','d','i','s','\0'],然后将字符串长度len修改为11
最终结构如下
{
len:11,
free:11,
buff:['h','e','l','l','o',',','r','e','d','i','s','\0']
}
ps:当长度小于1M是翻倍扩容,超过1M时是以1M为标准定量扩容
二是由长字符串变短
例如:由"hello,redis"变为"redis",这个时候会释放多余空间,同时把free值设为多出来的空间,以便下次使用方便
修改后的结构大概如下
{
len:5, // 字符串长度
free:17, // 原本11,加上释放到的6个字节
buff:['r','e','d','i','s','\0']
}
需要释放的时候可以手动调用函数来释放空间
为什么要使用sds?
- sds可以杜绝缓冲区溢出的问题,获取字符串长度复杂度为常数
- 二进制安全,sds使用len属性来判断字符串的结束
- 减少字符串修改时的内存重分配次数
链表
数据结构
//链表节点
typedef struct listNode{
struct listNode *pre;
struct listNode *next;
void *value;
}listNode;
//链表
typedef struct list{
listNode * head; //头节点
listNode * tail; //尾节点
unsigned long len; //节点数量
void *(*dup)(void *ptr); //节点值复制函数
void (*free)(void *ptr); //节点值释放函数
void (*match)(void *ptr,void *key); //节点值对比函数
}
链表是列表对象的底层实现之一
链表在redis中主要负责的是存储和维护某一类对象,所常用到的操主要有遍历,修改等
链表在redis中使用极为广泛,redis的事务,发布与订阅,服务器中维护的redisClient信息等都是用链表结构进行的存储
字典
数据结构
//哈希表
typedef struct dictht{
dictEntry **table; //哈希节点
unsigned long size; //哈希表大小
unsigned long sizemask; //哈希表掩码,用于计算索引值
unsigned long used; // 已有节点数量
} dictht;
//哈希节点
typedef struct dictEntry{
void *key;//键
union {
void *val;
uint64_tu64;
int64_ts64;
} v;
struct dictEntry *next;
}dictEntry;
//字典
typedef struct dict{
dictType *type; //类型特定函数
void *privdata; //私有数据
dictht ht[2]; //哈希表 ht[0]常用 ht[1]rehash时候使用
int rehashidx; //rehash标识
}dict;
字典是数据库的底层实现
解决键冲突
redis使用链地址法(separate chaining)来解决键冲突,当两个键的index值相同时,会把第二个键放到第一个键的前面,查询时对这个index的哈希节点链表进行遍历
rehash:
当哈希表的负载因子(load factor)大于设定值时(平时为1,在BGREWRITEAOF时为5),哈希表会进行rehash操作
rehash采用渐进式的方式进行执行,具体流程就是把ht[0]里面的数据重新进行哈希计算放到ht[1],此时的哈希查询操作两个表同时提供服务,写入操作则只有ht[1]提供,这样ht[0]处于只减不增的状态,最终当ht[0]里面的所有数据都被转移到ht[1]时,rehashidx被设为-1,表明rehash结束,删除ht[0],并将ht[1]设为ht[0],同时重新分配新的ht[1]
ps:负载因子 = used /size;
跳跃表
数据结构
//跳跃表节点
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[];
} zskiplistNode;
//跳跃表
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
跳跃表是有序集合的底层实现之一
跳跃表中的头结点不计算在length长度之内,跳跃表的节点排序按照分值从小到大排序
每次创建新节点的时候,redis会根据幂次定律随机生成一个1-32的层数作为level数组的大小,每个节点都有指向表尾方向的前进指针和之前表头方向的后退指针,这两个指针可以让程序方便的遍历所有节点,层的跨度用于记录两点之间的距离,跨度可以用来计算rank值.节点的分值是一个double值,节点的对象是一个指针,指向一个保存着sds字符串的字符串对象(下一节讲redis对象)
整数集合
数据结构
typedef struct intset{
uint32_t encoding;//编码方式
uint32_t length;//集合包含的元素数量
int8_t contents[];//保存元素的数组
} intset;
顾名思义整数集合是用来保存整数值的抽象数据结构
集合中不会出现重复元素
contents数组中保存的整数值有小到大排列
length等于contents的长度
虽然contents的定义是int8_t 但实际上contents的值类型由encoding决定
升级
当一个新元素超过原来整数集合encoding定义的值的类型时,会进行升级,升级结果会使集合的encoding变成所有数组中元素的值最大的数据类型,并且不支持降级
例如:有一个整数集合[1,2,3],本身的编码为int8,现在增加一个300的数字进该集合,会导致集合的编码升级为int16,这个时候列表的大小由8x3=24 变为 16x4=64,即便int8可以存储前三个值,但是为了简单起见,仍然会为集合中每一个元素分配同样的空间
压缩列表
压缩列表被用作列表键和哈希键的底层实现
压缩列表属于特殊的结构,是一种数据存储的方式,目的是为了节约内存,是一种采用特殊编码的连续内存块组成的顺序型(sequential)数据结构.
大致结构如下:
zlbytes | zltail | zllen | entry1 | entry2 | ... | zlend |
---|
每个压缩列表由如下三部分组成
previous_entry_length | encoding | content |
---|---|---|
前一节点的长度 | 记录content的类型和长度 | 节点的值 |
如果前一个节点长度小于254字节,previous_entry_length会使用1字节空间保存这个长度,如果大于254字节,将使用5字节长度保存这个值,这个机制会引起"连锁更新"
连锁更新: 假设现有连续的三个压缩列表节点l1,l2,l3,长度分别为 253,253,253,现在往第一个节点前添加一个长度超过254的节点,这个时候l1要给previous_entry_length分配5个字节来存储长度,所以列表本身长度会变为257,这将导致l2也需要5字节存储l1的长度,l3也会产生同样的变化,这样由一个列表操作引起的一系列更新操作成为连锁更新
Redis中的基本数据结构的更多相关文章
- Redis 中 5 种数据结构的使用场景介绍
这篇文章主要介绍了Redis中5种数据结构的使用场景介绍,本文对Redis中的5种数据类型String.Hash.List.Set.Sorted Set做了讲解,需要的朋友可以参考下 一.redis ...
- 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学习笔记之Redis中5种数据结构的使用场景介绍
原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 redis 中一共有5种数据结构 ...
- Redis 中的数据库
前面我们花了很多的时间介绍了 redis 中基本的数据结构,及其内部的实现情况,这些都是非常基础的东西,可能不经意间你就会用到他们,希望你花点时间了解一下. 接下来,我们将走近 redis 数据库,学 ...
- Redis解读(4):Redis中HyperLongLog、布隆过滤器、限流、Geo、及Scan等进阶应用
Redis中的HyperLogLog 一般我们评估一个网站的访问量,有几个主要的参数: pv,Page View,网页的浏览量 uv,User View,访问的用户 一般来说,pv 或者 uv 的统计 ...
- Redis 中 String 类型的内存开销比较大
使用 String 类型内存开销大 1.简单动态字符串 2.RedisObject 3.全局哈希表 使用 Hash 来存储 总结 参考 使用 String 类型内存开销大 如果我们有大量的数据需要来保 ...
- Redis中的数据结构与常用命令
开发系统:Ubuntu 17.04Redis驱动:StackExchange.Redis 1.2.3Redis版本:3.2.1开发平台:.NET Core 对于Redis的介绍这里只写一句:Redis ...
随机推荐
- jenkins 设置自动发送邮件
1.测试邮件发送 进入jenkins系统管理-->系统设置做如下配置 1.设置系统管理员邮件地址,这是一个全局变量,意味着所有的jenkins执行的任务,最后都会通过这个邮件地址发送邮件 ...
- IOS中的绘图Quartz2D
drawRect 方法的使用 常见图形的绘制:线条.多边形.圆 绘图状态的设置:文字颜色.线宽等 图形上下文状态的保存与恢复 图形上下文栈 Quartz 2D是一个二维绘图引擎,同时支持IOS和MAC ...
- Numpy的使用
Numpy的主要功能: 可以观察以上的规律,会发现,代码类型的简写,计量都是以8作为起始1的. # -*- coding: utf-8 -*- #向量相加-Python def pythonsum(n ...
- 关于股票最佳买卖时机的lintcode代码
class Solution {public: /** * @param prices: Given an integer array * @return: Maximum pr ...
- LeetCode4. Median of Two Sorted Arrays---vector实现O(log(m+n)--- findkth
这道题目和PAT上的1029是同一题.但是PAT1029用O(m+n)的时间复杂度(题解)就可以,这道题要求是O(log(m+n)). 这道题花费了我一个工作日的时间来思考.因为是log因而一直思考如 ...
- 浏览器兼容性--new Date
ie浏览器下new Date("2013/04")与new Date("2016-04")会报错: //将201601格式的字符串转为Date对象,月份从0开始 ...
- bash shell中测试命令
bash shell中测试命令 test命令提供了if-than语句中测试不同条件的途径.如果test命令中列出的条件成立,test命令就会退出并返回退出状态吗0 .这样if-than语句就与其他编程 ...
- java 解决时间相减问题
比如 Date currentTime ="2011-06-20 9:23:50"; Date endTime="2011-06-21 10:33:56"; 要 ...
- 一天搞定HTML----a标签02
1.细说a标签 2.代码演示 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> ...
- javaSE_05Java中方法(函数)与重载、递归
1.方法的声明和调用 什么是方法?为什么需要方法?代码复用,方便软件升级 什么是方法? 具备特定功能的一段独立的代码段 标准的方法格式:(注意格式的顺序) 修饰符 返回值类型 方法名(参数类型 参数名 ...