一、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[]内,通过偏移来访问

graph TB
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和链表的更多相关文章

  1. Redis数据结构之链表

    Redis使用的链表是双向无环链表,链表节点可用于保存各种不同类型的值. 一.链表结构定义1. 链表节点结构定义: 2. 链表结构定义: 示例: 二.链表在Redis中的用途1. 作为列表键的底层实现 ...

  2. Redis 的底层数据结构(SDS和链表)

    Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件.可能几乎所有的线上项目都会使用到 Redis,无论你是做缓存.或是用作消息中间件,用起来很简单方便 ...

  3. Redis数据结构:链表

    链表被广泛用于Redis的各种功能,比如列表键.发布与订阅.慢查询.监视器等. 每个链表节点由一个listNode结构表示,每个节点都有前置节点和后置节点. 每个链表使用一个list结构来表示,这个结 ...

  4. Redis实现之链表

    链表 链表提供了高效的节点重排能力,以及顺序性的节点访问顺序,并且可以通过增删节点来灵活地调整链表的长度.作为一种常用数据结构,链表内置在很多高级的编程语言里面,因为Redis使用的C语言并没有内置这 ...

  5. redis系列之------链表

    前言 链表提供了高效的节点重排能力, 以及顺序性的节点访问方式, 并且可以通过增删节点来灵活地调整链表的长度. 作为一种常用数据结构, 链表内置在很多高级的编程语言里面, 因为 Redis 使用的 C ...

  6. C基础 带你手写 redis sds

    前言 - Simple Dynamic Strings  antirez 想统一 Redis,Disque,Hiredis 项目中 SDS 代码, 因此构建了这个项目 https://github.c ...

  7. Redis SDS 深入一点,看到更多!

    1.什么是SDS? Redis 自定的字符串存储结构,关于redis,你需要了解的几点!中我们对此有过简要说明. Redis 底层是用C语言编写的,可是在字符存储上,并未使用C原生的String类型, ...

  8. 深入理解Redis 数据结构—双链表

    在 Redis 数据类型中的列表list,对数据的添加和删除常用的命令有 lpush,rpush,lpop,rpop,其中 l 表示在左侧,r 表示在右侧,可以在左右两侧做添加和删除操作,说明这是一个 ...

  9. Redis的List链表类型命令

    List是一个链表结构,主要功能是push.pop.获取一个范围的所有值等等,操作中key理解为链表的名字.list类型其实就是一个每个子元素都是string类型的双向链表.我们可以通过push.po ...

随机推荐

  1. Python函数之面向过程编程

    一.解释 面向过程:核心是过程二字,过程即解决问题的步骤,基于面向过程去设计程序就像是在设计,流水线式的编程思想,在设计程序时,需要把整个流程设计出来, 一条工业流水线,是一种机械式的思维方式 二.优 ...

  2. Django-利用Form组件和ajax实现的注册

    利用Form组件和ajax实现的注册 一.注册相关的知识点 1.Form组件 我们一般写Form的时候都是把它写在views视图里面,那么他和我们的视图函数也不影响,我们可以吧它单另拿出来,在应用下面 ...

  3. Java 添加、读取和删除 Excel 批注

    批注是一种富文本注释,常用于为指定的Excel单元格添加提示或附加信息. Free Spire.XLS for Java 为开发人员免费提供了在Java应用程序中对Excel文件添加和操作批注的功能. ...

  4. 【Net】ABP框架学习之它并不那么好用

    前言 上一篇文章介绍了ABP的Web API,本文在继续介绍ABP的其他内容. 在ABP中,WEBAPI是一个值得用的东西.但其他东西,就不一定是那么好用了. 下面我们看一下ABP的Controlle ...

  5. Docker的MySQL镜像, 实行数据,配置信息,日志持久化

    Docker的MySQL8镜像, 实行数据持久化 使用Docker的MySQL8.0.17实例化一个容器之后需要对其进行数据持久化操作, 使用 docker docker run -p 7797:33 ...

  6. P3381 【模板】最小费用最大流 题解

    CSDN同步 原题链接 前置知识: 从三种算法剖析网络流本质 简要题意: 给定网络图,求图的最大流,以及流量为最大流时的最小费用. 现在假设你们看了那篇网络流博客之后,所有人都会了 \(\text{E ...

  7. PTA | 1005 继续(3n+1)猜想 (25分)

    卡拉兹(Callatz)猜想已经在1001中给出了描述.在这个题目里,情况稍微有些复杂. 当我们验证卡拉兹猜想的时候,为了避免重复计算,可以记录下递推过程中遇到的每一个数.例如对 n=3 进行验证的时 ...

  8. 字符串操作 — Google Guava

    前言 Java 里字符串表示字符的不可变序列,创建后就不能更改.在我们日常的工作中,字符串的使用非常频繁,熟练的对其操作可以极大的提升我们的工作效率,今天要介绍的主角是 Google 开源的一个核心 ...

  9. android开发对应高德地图定位服务进度一

    进行android的高德地图开发首先需要进入高德地图的控制台进行注册登录.之后创建新的应用并且绑定软件得到相应的key. 这里面需要找到自己软件对应的多个SHA1.这里有发布版和调试版,以及对应的软件 ...

  10. Windows10操作技巧系列——删除最常用,最常访问,快速访问记录

    Win10除了有传统意义上的,网络历史记录外,还包含了两种本地文件浏览记录,分别是资源管理器中的“快速访问”记录,和开始菜单以及任务栏中的“最常用”“最近”“最常访问”等“最记录”. 资源管理器中的“ ...