前言
redis源码只有23000行代码, 可以说压缩的代码非常经典, 以最少代码写出存储管理
正文
一, redis 中内存管理
redis中提供接口有
void *zmalloc(size_t size);
void *zcalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void zfree(void *ptr);
char *zstrdup(const char *s);
//已经使用内存的大小
size_t zmalloc_used_memory(void);
void zmalloc_enable_thread_safeness(void);
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
float zmalloc_get_fragmentation_ratio(size_t rss);
// 获取
size_t zmalloc_get_rss(void);
size_t zmalloc_get_private_dirty(void);
size_t zmalloc_get_smap_bytes_by_field(char *field);
void zlibc_free(void *ptr);
#ifndef HAVE_MALLOC_SIZE
size_t zmalloc_size(void *ptr);
#endif
zmalloc(size)在分配内存的时候会多申请sizeof(size_t)个字节大小的内存
【64位系统中是8字节】,即调用malloc(size+8),
所以一共申请分配size+8个字节,zmalloc(size)会在已分配内存的首地址开始的8字节
中存储size的值,实际上因为内存对齐,malloc(size+8)分配的内存可能会比size+8要多一些,
目的是凑成8的倍数,所以实际分配的内存大小是size+8+X【(size+8+X)%8==0 (0<=X<=7)】。
然后内存指针会向右偏移8个字节的长度。zfree()就是zmalloc()的一个逆操作,
而zmalloc_size()的目的就是计算出size+8+X的总大小
size&(sizeof(long) - 1)
二, 动态字符串sds.h
字符串结构体
typedef char *sds;
//柔性数组成员不占用结构体的空间,只作为一个符号地址存在,而且必须是结构体的最后一个成员。
//柔性数组成员不仅可以用于字符数组,还可以是元素为其它类型的数组。
//C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,
//但结构中的柔性数组成员前面必须至少一个其他成员。柔性数组成员允许结构中包含一个大小可变的
//数组。sizeof返回的这种结构大小不包括柔性数组的内存。包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
/************************************************************************/
/* sizeof(struct sdshdr) == 8 */
/************************************************************************/
struct sdshdr {
unsigned int len;// 记录 buf 数组中已使用字节的数量// 等于 SDS 所保存字符串的长度
unsigned int free;// 记录 buf 数组中未使用字节的数量
char buf[]; // ==> char buf[0] // 字节数组,用于保存字符串
};
sdsMakeRoomFor 方法是 申请内存 分为两个方向 扩充 分别是:
- newlen = (老的数据的大小 + addlen ) * 2
- newlen = 老的数据的大小 + addlen + SDS_MAX_PREALLOC
sds sdsMakeRoomFor(sds s, size_t addlen)
{
struct sdshdr *sh, *newsh;
size_t free = sdsavail(s);
size_t len, newlen;
if (free >= addlen)
{
return s;
}
len = sdslen(s);
sh = (void*) (s-(sizeof(struct sdshdr)));
newlen = (len + addlen); // new ---> old + new_len
if (newlen < SDS_MAX_PREALLOC)
{
newlen *= 2;
}
else
{
newlen += SDS_MAX_PREALLOC; /// [1024 * 1024] 1M
}
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
if (newsh == NULL)
{
return NULL;
}
newsh->free = newlen - len;
return newsh->buf;
}
格式化输出
关于va_开头使用方法可以看我这份博客: https://blog.csdn.net/Poisx/article/details/78822693
方法一: sdscatvprintf
// 得到
sds sdscatvprintf(sds s, const char *fmt, va_list ap)
{
va_list cpy;
char staticbuf[1024], *buf = staticbuf, *t;
size_t buflen = strlen(fmt) * 2;
/* We try to start using a static buffer for speed.
* If not possible we revert to heap allocation. */
if (buflen > sizeof(staticbuf))
{
buf = zmalloc(buflen);
if (buf == NULL)
{
return NULL;
}
}
else
{
buflen = sizeof(staticbuf);
}
/* Try with buffers two times bigger every time we fail to
* fit the string in the current buffer size. */
while(1)
{
buf[buflen-2] = '\0';
va_copy(cpy,ap);
vsnprintf(buf, buflen, fmt, cpy);
va_end(cpy);
// 判断 buf 大小是否可以放的下 cpy的数据, 不够从新申请内存
if (buf[buflen-2] != '\0')
{
if (buf != staticbuf)
{
zfree(buf);
}
buflen *= 2;
buf = zmalloc(buflen);
if (buf == NULL)
{
return NULL;
}
continue;
}
break;
}
/* Finally concat the obtained string to the SDS string and return it. */
t = sdscat(s, buf);
if (buf != staticbuf)
{
zfree(buf);
}
return t;
}
方法二:sdscatfmt
sds sdscatfmt(sds s, char const *fmt, ...)
{
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
size_t initlen = sdslen(s);
const char *f = fmt;
int i;
va_list ap;
va_start(ap,fmt);
f = fmt; /* Next format specifier byte to process. */
i = initlen; /* Position of the next byte to write to dest str. */
while(*f)
{
char next, *str;
unsigned int l;
long long num;
unsigned long long unum;
/* Make sure there is always space for at least 1 char. */
if (sh->free == 0)
{
s = sdsMakeRoomFor(s,1);
sh = (void*) (s-(sizeof(struct sdshdr)));
}
switch(*f) {
case '%':
next = *(f+1);
f++;
switch(next) {
case 's':
case 'S':
str = va_arg(ap,char*);
l = (next == 's') ? strlen(str) : sdslen(str);
if (sh->free < l)
{
s = sdsMakeRoomFor(s,l);
sh = (void*) (s-(sizeof(struct sdshdr)));
}
memcpy(s+i,str,l);
sh->len += l;
sh->free -= l;
i += l;
break;
case 'i':
case 'I':
if (next == 'i')
{
num = va_arg(ap, int);
}
else
{
num = va_arg(ap, long long);
}
{
char buf[SDS_LLSTR_SIZE];
l = sdsll2str(buf,num);
if (sh->free < l)
{
s = sdsMakeRoomFor(s,l);
sh = (void*) (s-(sizeof(struct sdshdr)));
}
memcpy(s+i,buf,l);
sh->len += l;
sh->free -= l;
i += l;
}
break;
case 'u':
case 'U':
if (next == 'u')
{
unum = va_arg(ap, unsigned int);
}
else
{
unum = va_arg(ap, unsigned long long);
}
{
char buf[SDS_LLSTR_SIZE];
l = sdsull2str(buf, unum);
if (sh->free < l) {
s = sdsMakeRoomFor(s,l);
sh = (void*) (s-(sizeof(struct sdshdr)));
}
memcpy(s+i,buf,l);
sh->len += l;
sh->free -= l;
i += l;
}
break;
default: /* Handle %% and generally %<unknown>. */
s[i++] = next;
sh->len += 1;
sh->free -= 1;
break;
}
break;
default:
s[i++] = *f;
sh->len += 1;
sh->free -= 1;
break;
}
f++;
}
va_end(ap);
/* Add null-term */
s[i] = '\0';
return s;
}
int转换 string类型的数据
这个工具很好使用的在C++ 相当于 stringostream工具类
#define SDS_LLSTR_SIZE 21
int sdsll2str(char *s, long long value)
{
char *p, aux;
unsigned long long v;
size_t l;
/* Generate the string representation, this method produces
* an reversed string. */
v = (value < 0) ? -value : value;
p = s;
do
{
*p++ = '0'+(v%10); // assic 一个字节 = 8bit // string hex -> 是不能超过9的数字的
v /= 10;
} while(v);
if (value < 0)
{
*p++ = '-';
}
/* Compute length and add null term. */
l = p-s;
*p = '\0';
/* Reverse the string. */
p--;
while(s < p)
{
aux = *s;
*s = *p;
*p = aux;
s++;
p--;
}
return l;
}
下面内存中数据
34 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 43..............
,
=, p =
[110] num = 2, v = 293918262, p =
34 33 38 00 00 00 00 00 00 00 00 00 00 00 00 00 438.............
,
=, p =
[110] num = 3, v = 458888555, p =
34 33 38 38 00 00 00 00 00 00 00 00 00 00 00 00 4388............
比较字符串出现
/*
Example:
*
* s = sdsnew("AA...AA.a.aa.aHelloWorld :::");
* s = sdstrim(s,"A. :");
* printf("%s\n", s);
*
* Output will be just "Hello World".*/
sds sdstrim(sds s, const char *cset)
{
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
char *start, *end, *sp, *ep;
size_t len;
sp = start = s;
ep = end = s + sdslen(s)-1;
/************************************************************************/
/* char* strchr(char *s, char c)
{
while(*s != '\0' && *s != c)
{
++s;
}
return *s==c ? s : NULL;
}
*/
/************************************************************************/
while (sp <= end && strchr(cset, *sp)) // 比较的是直到 *sp中字符不可能出现在cset字符串中
{
sp++;
}
while (ep > start && strchr(cset, *ep))
{
ep--;
}
len = (sp > ep) ? 0 : ((ep-sp)+1);
if (sh->buf != sp)
{
memmove(sh->buf, sp, len);
}
sh->buf[len] = '\0';
sh->free = sh->free+(sh->len-len);
sh->len = len;
return s;
}
在文件sds.c中,给出了所有操作sds字符串的操作,主要包括以下操作
function | 翻译 |
---|---|
sdsnew | 字符串新建 |
sdsfree | 字符串释放 |
sdsdup | 字符串复制 |
sdssetlen | 字符串的长度更新 |
sdsinclen | 字符串增加长度 |
sdsalloc | 得到字符串分配的内存长度(仅仅是用来存储字符串的部分的长度) |
sdsmakeroomfor | 当字符串长度增加时,用来扩展字符串,值得注意的是,当出现类型提升(比如从sdshdr8提升到sdshdr16),代码会有相应的变化 |
sdsremoveFreeSpace | 压缩字符串多余的存储空间,同上,注意出现的类型下降相关的代码 |
sdstrim | 删除掉字符串2头的在指定字符集合内的字符 |
sdsrange | 将字符串内容设置为指定区间内的字符,注意其中copy字符串时,使用的是memmove函数 |
sdscatlen | 为当前字符串增加新的内容。 |
sdssplitlen | 将字符串按照分隔符,切割成若干个sds字符串。 |
sdssplitargs | 将命令行字符串的参数解析出来,将其设置为一个sds数组的形式,返回给调用者。 |
sdsmapchars | sds字符串内按照[from, to]的字符map映射进行转换 |