[redis]SDS和链表
一、SDS
1、SDS结构体
redis3.2之前:不管buf的字节数有多少,都用 4字节的len来储存长度,对于只存短字符串那么优点浪费空间,比如只存 name
,则len=4
则只需要一个字节8位即可表示
struct sdshdr {
unsigned int len; // buf中已占字节数
unsigned int free; // buf中剩余字节数
char buf[]; // 数据空间
};
redis3.2之后:
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; //已分配字节数
uint8_t alloc; //剩余字节数
unsigned char flags; //标识属于那种类型的SDS 低3存类型,高5不使用
char buf[];
};
//........16、32、64
_attribute_ ((_packed_)) 关键字是为了取消字节对齐
struct test1 {
char c;
int i;
}; struct __attribute__ ((__packed__)) test2 {
char c;
int i;
}; int main()
{
cout << "size of test1:" << sizeof(struct test1) << endl; //5
cout << "size of test2:" << sizeof(struct test2) << endl; //8
}
注意,这些结构都存在一个 char[]内,通过偏移来访问
subgraph
header-->buf
end
2、重要函数解析
sdsReqType
确定类型:sdsReqType根据传入的 char[] 长度来缺点应该用哪种类型的 SDS结构体来描述
static inline char sdsReqType(size_t string_size) {
if (string_size < 1<<5)
return SDS_TYPE_5;
if (string_size < 1<<8) //8位 表示长度范围 0-256
return SDS_TYPE_8;
if (string_size < 1<<16) //16位
return SDS_TYPE_16;
#if (LONG_MAX == LLONG_MAX)
if (string_size < 1ll<<32)
return SDS_TYPE_32;
return SDS_TYPE_64;
#else
return SDS_TYPE_32;
#endif
}
sdsnewlen
根据长度结构化 char数组,创建一个 长度为 str本身长度+head长度的数组, sdsnew就是调用这个来创建sds字节数组的
sds sdsnewlen(const void *init, size_t initlen) {
void *sh; //存放sds header数据的头指针
sds s; //char* s
char type = sdsReqType(initlen); //根据str长度 确定SDS header类型
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
int hdrlen = sdsHdrSize(type); //header 长度
unsigned char *fp; //类型指针
sh = s_malloc(hdrlen+initlen+1); //分配 str长度+header长度的内存空间
...
memset(sh, 0, hdrlen+initlen+1); //初始化空间
s = (char*)sh+hdrlen; //移动到header之后的buf首地址位置
fp = ((unsigned char*)s)-1; //移动到header的尾部 标识sds header类型
switch(type) {
....
case SDS_TYPE_8: {
//#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
//sh指向header空间头部位置 s代表buf首地址 下面将sh移动到header的首地址
SDS_HDR_VAR(8,s); //struct sdshdr8* sh = (void*)(s-sizeof(header))
sh->len = initlen; //填充数据
sh->alloc = initlen;
*fp = type;//类型数据填充
break;
}
......
}
if (initlen && init)
memcpy(s, init, initlen); //将str数据复制到buf中
s[initlen] = '\0';
return s;
}
sdslen、sdsavail
返回使用和未使用的空间。 **根据头部类型转化指针,然后直接 sh->len 和 sh->alloc-sh->len **即可求出
sdscat、sdscatlen、sdsMakeRoomFor
将 t
拼接到 s
中,
sds sdscatsds(sds s, const sds t) {
return sdscatlen(s, t, sdslen(t));
}
sds sdscatlen(sds s, const void *t, size_t len) {
size_t curlen = sdslen(s);
s = sdsMakeRoomFor(s,len); //保证空间充足
if (s == NULL) return NULL;
memcpy(s+curlen, t, len); //直接copy
sdssetlen(s, curlen+len); //设置新的长度
s[curlen+len] = '\0';
return s;
}
sdsMakeRoomFor
是为了保证空间充足,如果不充足进行扩容,下面就是newlen的核心代码,会扩容大于需要的长度,防止多次扩容。体现了 预先分配
扩容是一个耗时的操作
if (avail >= addlen) return s;
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC) //#define SDS_MAX_PREALLOC (1024*1024)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
sdstrim
将cset中在s出现的删除,这个函数就体现了 惰性释放 ,不会缩减空间,仅仅改变 len,同时也体现了 和 c的兼容性,可以用 系统strings函数来操作 sds
sds sdstrim(sds s, const char *cset) {
char *start, *end, *sp, *ep;
size_t len;
sp = start = s;
ep = end = s+sdslen(s)-1;
while(sp <= end && strchr(cset, *sp)) sp++;
while(ep > sp && strchr(cset, *ep)) ep--;
len = (sp > ep) ? 0 : ((ep-sp)+1);
if (s != sp) memmove(s, sp, len);
s[len] = '\0';
sdssetlen(s,len);
return s;
}
3、优点
A.获取长度方便
c字符串获取长度需要便利char数组,O(n),而SDS结构体记录了长度,不需要char数组即可知道长度。
B.防止溢出
char数组不知道还有多少空间空余,可能会在两个字符串拼接的时候溢出,而SDS记录了未使用的空间,可以有效的分配扩容,防止溢出。
C.内存分配方便和使用高效
传统c的char数组,如果空间不足,需要手动扩容,然后复制原数据,截断时,也需要缩减空间,来防止内存泄漏。但是SDS可以进行 空间预分配、惰性释放 等策略来搞效的使用内存。
空间预分配:
预先分配足够的空间,减少扩容次数
惰性释放
因为SDS记录了 free未分配空间字段,所以截断字符串的时候不需要立即复制元素进行缩减,直接增加 free 数值,减少 len即可,后面要增加字符串只增加len,减少free ,覆盖写入即可。(free = alloc-len)
D.兼容C
SDS只是增加了两个字段,其实数据还是存在 char[] buf里面的,所以可以使用 c内置的字符串处理函数来处理 SDS底层字节数组。
typedef char *sds;
所以在处理 字符串的API里只是传入了 char* 来处理字符串。空间是否充足都有额外的信息来描述。
二、链表
链表的话可以参考我的 https://www.cnblogs.com/biningooginind/p/12553163.html
基本参照了redis的链表操作。
1、结构体
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value; //void* 指针 可以存放任意类型的数据
} listNode;
2、特点
链表的特点:
- 删除、插入 O(1)
- 遍历访问 O(n)
- 有head和tail指针,将访问最后一个元素复杂度降低到O(1)
- 带有 len长度,方便知道链表的长度
- 双链表结构,前后遍历都方便
- 无环
- 多态:数据用 void 来指向,可以存放任意类型数据,不用为每个类型都写一个链表*
- 迭代器模式,链表有一个迭代器,方便遍历节点
typedef struct listIter {
listNode *next; //下一个节点
int direction; //遍历方向 forward or backward
} listIter;
[redis]SDS和链表的更多相关文章
- Redis数据结构之链表
Redis使用的链表是双向无环链表,链表节点可用于保存各种不同类型的值. 一.链表结构定义1. 链表节点结构定义: 2. 链表结构定义: 示例: 二.链表在Redis中的用途1. 作为列表键的底层实现 ...
- Redis 的底层数据结构(SDS和链表)
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件.可能几乎所有的线上项目都会使用到 Redis,无论你是做缓存.或是用作消息中间件,用起来很简单方便 ...
- Redis数据结构:链表
链表被广泛用于Redis的各种功能,比如列表键.发布与订阅.慢查询.监视器等. 每个链表节点由一个listNode结构表示,每个节点都有前置节点和后置节点. 每个链表使用一个list结构来表示,这个结 ...
- Redis实现之链表
链表 链表提供了高效的节点重排能力,以及顺序性的节点访问顺序,并且可以通过增删节点来灵活地调整链表的长度.作为一种常用数据结构,链表内置在很多高级的编程语言里面,因为Redis使用的C语言并没有内置这 ...
- redis系列之------链表
前言 链表提供了高效的节点重排能力, 以及顺序性的节点访问方式, 并且可以通过增删节点来灵活地调整链表的长度. 作为一种常用数据结构, 链表内置在很多高级的编程语言里面, 因为 Redis 使用的 C ...
- C基础 带你手写 redis sds
前言 - Simple Dynamic Strings antirez 想统一 Redis,Disque,Hiredis 项目中 SDS 代码, 因此构建了这个项目 https://github.c ...
- Redis SDS 深入一点,看到更多!
1.什么是SDS? Redis 自定的字符串存储结构,关于redis,你需要了解的几点!中我们对此有过简要说明. Redis 底层是用C语言编写的,可是在字符存储上,并未使用C原生的String类型, ...
- 深入理解Redis 数据结构—双链表
在 Redis 数据类型中的列表list,对数据的添加和删除常用的命令有 lpush,rpush,lpop,rpop,其中 l 表示在左侧,r 表示在右侧,可以在左右两侧做添加和删除操作,说明这是一个 ...
- Redis的List链表类型命令
List是一个链表结构,主要功能是push.pop.获取一个范围的所有值等等,操作中key理解为链表的名字.list类型其实就是一个每个子元素都是string类型的双向链表.我们可以通过push.po ...
随机推荐
- UVA11300 Spreading the Wealth 数学
前方数学警告 题目链接:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=25&am ...
- Blazor入门笔记(3)-C#与JS交互
1.环境 VS2019 16.5.1 .NET Core SDK 3.1.200 Blazor WebAssembly Templates 3.2.0-preview2.20160.5 2.前言 Bl ...
- 记一次Windb死锁排查
正在开会,突然线上站点线程数破千.然后一群人现场dump分析. 先看一眼线程运行状态 !eeversion 发现CPU占用并不高,19%,937条线程正在运行. 看看他们都在干什么. ~* e !cl ...
- PTA数据结构与算法题目集(中文) 7-25
PTA数据结构与算法题目集(中文) 7-25 7-25 朋友圈 (25 分) 某学校有N个学生,形成M个俱乐部.每个俱乐部里的学生有着一定相似的兴趣爱好,形成一个朋友圈.一个学生可以同时属于若干 ...
- 用人话告诉小白:什么是项目管理(例如Maven),什么是调试工具(即debugger),什么是编译(即compile)
项目管理 以java程序的项目管理软件Maven为例,java程序根据代码的不同需要不同的jar文件才能编译运行. 人物:两个程序员A和B 物品:一个java程序G,许多jar文件 场景:当A在自己电 ...
- VSCode 初次写vue项目并一键生成.vue模版
VSCode 写vue项目一键生成.vue模版 1.新建代码片段 文件-->首选项-->用户代码片段-->点击新建代码片段--取名vue.json 确定 2.配置快捷生成的vue模板 ...
- 初始WebApi(1)
如果你要问我WebApi是干嘛,我只能说它是的给数据.哈哈哈哈哈,这几天也才刚刚了解了解关于WebApi的知识,今天就来谈谈吧. 1.创建WebApi项目 第一步:选择ASP.NET Web应用程序 ...
- vue中axios的安装使用
axios是一个基于 promise 的 HTTP 库,在vue中axios是比较常用的网络请求方法. 安装 npm install axios -S 在main.js配置 import axios ...
- CocoaPods应用于iOS项目框架管理方案
- 2019-07-31【机器学习】无监督学习之降维NMF算法 (人脸特征提取)
代码 from numpy.random import RandomState #加载RandomState用于创建随机种子 import matplotlib.pyplot as plt from ...