转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com
本文使用的Redis 5.0源码
概述
最近在通过 Redis 学 C 语言,不得不说,Redis的代码写的真的工整。这篇文章会比较全面的深入的讲解了Redis数据结构字符串的源码实现,希望大家能够从中学到点东西。
Redis 的字符串源码主要都放在了 sds.c
和 sds.h
这两个文件中。具体实现已经被剥离出来变成单独的库:https://github.com/antirez/sds。
Redis 的动态字符串结构如下图所示:
SDS 大致由两部分构成:header以及 数据段,其中 header 还包含3个字段 len、alloc、flags。len 表示数据长度,alloc 表示分配的内存长度,flags 表示了 sds 的数据类型。
在以前的版本中,sds 的header其实占用内存是固定8字节大小的,所以如果在redis中存放的都是小字符串,那么 sds 的 header 将会占用很多的内存空间。
但是随着 sds 的版本变迁,其实在内存占用方面还是做了一些优化:
- 在 sds 2.0 之前 header 的大小是固定的 int 类型,2.0 版本之后会根据传入的字符大小调整 header 的 len 和 alloc 的类型以便节省内存占用。
- header 的结构体使用
__attribute__
修饰,这里主要是防止编译器自动进行内存对齐,这样可以减少编译器因为内存对齐而引起的 padding 的数量所占用的内存。
目前的版本中共定义了五种类型的 sds header,其中 sdshdr5 是没用的,所以没画:
源码分析
sds 的创建
sds 的创建主要有如下几个函数:
sds sdsnewlen(const void *init, size_t initlen); sds sdsnew(const char *init); sds sdsempty(void); sds sdsdup(const sds s);
-
sdsnewlen 传入一个 C 的字符串进去,创建一个 sds 字符串,并且需要传入大小;
-
sdsnew 传入一个 C 的字符串进去,创建一个 sds 字符串,它里面实际上会调用
strlen(init)
计算好长度之后调用 sdsnewlen; -
sdsempty 会创建一个空字符串 “”;
-
sdsdup 调用 sdsnewlen,复制一个已存在的字符串;
所以通过上面的创建可以知道,最终都会调用 sdsnewlen 来创建字符串,所以我们看看这个函数具体怎么实现。
sdsnewlen
其实对于一个字符串对象的创建来说,其实就做了三件事:申请内存、构造结构体、字符串拷贝到字符串内存区域中。下面我们看一下 Redis 的具体实现。
申请内存
sds sdsnewlen(const void *init, size_t initlen) { void *sh; //指向SDS结构体的指针 sds s; //sds类型变量,即char*字符数组 char type = sdsReqType(initlen); //根据数据大小获取sds header 类型 if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; int hdrlen = sdsHdrSize(type); // 根据类型获取sds header大小 unsigned char *fp; /* flags pointer. */ assert(hdrlen+initlen+1 > initlen); /* Catch size_t overflow */ sh = s_malloc(hdrlen+initlen+1); //新建SDS结构,并分配内存空间,这里加1是因为需要在最后加上 ... return s; }
在内存申请之前,需要做的事情有以下几件:
- 因为 sds 会根据传入的大小来设计 header 的类型,所以需要调用 sdsReqType 函数根据 initlen 获取 header 类型;
- 然后调用 sdsHdrSize 根据 header 类型获取 header 占用的字节数;
- 最后调用 s_malloc 根据 header 长度和字符串长度分配内存,这里需要加1是因为在 c 里面字符串是以