redis 数据结构基础 (一) 字符串

字符串是Redis中最常用的数据结构,今天研究了一下redis中字符串的代码。

首先,如下是最简单的设置key的过程

redis内部将长度小于等于39的字符串编码为embstr格式,其中,embstr是embeded string的缩写,而将长度大于等于40的字符串编码为raw格式。

之所以边界值为39,是因为redis内部并没有使用原始的malloc函数来分配内存,而是使用了jemalloc函数来分配内存,jemalloc是

facebook于2012年提出的内存分配解决方案 ,据说变Linux提供的原始的malloc方案的性能提高了20%左右,在 多核CPU上性能

非常优异,jemalloc函数在分配内存时,64是个比较敏感的数字。

见如下代码:

1 robj *createStringObject(const char *ptr, size_t len) {
2     if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
3         return createEmbeddedStringObject(ptr,len);
4     else
5         return createRawStringObject(ptr,len);
6 }

值得注意的是此函数的返回值是robj*对象,此结构体是整个redis代码中最重要的结构体之一,他代表着一个数据对象(其实就是Redis Object的缩写),

目前大家仅仅只需要知道有这么个概念存在就好,到后面我会专门开一小节来讨论什么是数据对象。

createEmbeddedStringObject函数的实现如下:
 1 robj *createEmbeddedStringObject(const char *ptr, size_t len) {
 2     robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1); 1⃣️
 3     struct sdshdr8 *sh = (void*)(o+1);
 4 
 5     o->type = OBJ_STRING;2⃣️
 6     o->encoding = OBJ_ENCODING_EMBSTR;
 7     o->ptr = sh+1;
 8     o->refcount = 1;
 9     if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
10         o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
11     } else {
12         o->lru = LRU_CLOCK();
13     }
14 
15     sh->len = len; 3⃣️
16     sh->alloc = len;
17     sh->flags = SDS_TYPE_8;
18     if (ptr) {
19         memcpy(sh->buf,ptr,len);4⃣️
20         sh->buf[len] = '\0';
21     } else {
22         memset(sh->buf,0,len+1);
23     }
24     return o;
25 }

上面的代码主要分为4个部分

1、分配内存,注意此处仅仅使用了一次分配内存的函数,在内存布局上,需要存储的字符串对象紧跟在robj对象之后

2、设置robj对象的基本信息

3、设置sds字符串的基本信息

4、复制字符串内容到分配好的内存空间。

createRawStringObject函数的实现如下:
1 robj *createRawStringObject(const char *ptr, size_t len) {
2     return createObject(OBJ_STRING, sdsnewlen(ptr,len));
3 }

 

其中,createObject的代码的功能是创建一个RedisObject对象,附上基本信息,重要的是对其中的robj.ptr成员赋值,
该值由
sdsnewlen函数提供。sds是redis对字符串的内部封装格式,有趣的是,sds字符串并不是一个真实的类型,而是多
个类型的共用名称:
sds字符串由4部分组成
1、哪一种sds类型(1字节)
2、sds的头信息(可变长度)
3、字符串的内容 (可变长度)
4、字符串末尾结束符"\0"
通常情况下,使用sds字符串时,是从sds字符串的第二个字节开始用起,第一个字节是隐藏在代码中的。

其中,sds的头部有5种类型:sdshdr5,sdshdr8,sdshdr16, sdshdr32, sdshdr64
除了sdshdr5之外,其它4个的成员定义都是类似的,我们以sdshdr8为例:
1 struct __attribute__ ((__packed__)) sdshdr8 {
2     uint8_t len; /* used */
3     uint8_t alloc; /* excluding the header and null terminator */
4     unsigned char flags; /* 3 lsb of type, 5 unused bits */
5     char buf[];
6 };

 

其中,len代表字符串的长度,alloc是为字符串分配的空间,flags是掩码,buf是字符串内容,紧跟在sds的头部之后。
sdshdr5结构体中去除了len和alloc成员变量。此时字符串长度由flags成员的前5个bit确定。
redis采用这样的结构,主要的目的是在于使用短字符串的时候,可以节省字符串头部的若干字节的内存,然而sds字符串
还有32bit长度和64bit的区分,如果真的出现了字符串超过4G的长度,感觉节省那几个字符也没多大意思。

redis中对整数进行的处理:
在redis中,整数的存储按3种策略进行存储:
1、数值在0-10000之间的数字,将使用预先设置好的shared.integers[]全局变量,共享全局变量还存在一个引用计数的问题,此处不再展开
2、在LONG_MIN和LONG_MAX之间的数字,将使用强制转换的办法,直接用ptr成员来存储,显然读取的时候,也要将此ptr强制转换为数字
3、更大或者更小的数字,都会存储为字符串的格式
此过程可以在函数createStringObjectFromLongLong中得出。其中,1、2所存储的数据encoding类型均为INT,3存储的数据类型则为RAW

redis将所有的浮点数也存储为字符串。此处不再展开。

在文件sds.c中,给出了所有操作sds字符串的操作,主要包括以下操作

sdsnew 字符串新建
sdsfree 字符串释放
sdsdup 字符串复制
sdssetlen 字符串的长度更新
sdsinclen 字符串增加长度
sdsalloc 得到字符串分配的内存长度(仅仅是用来存储字符串的部分的长度)
sdsmakeroomfor 当字符串长度增加时,用来扩展字符串,值得注意的是,当出现类型提升(比如从sdshdr8提升到sdshdr16),
代码会有相应的变化
sdsremoveFreeSpace 压缩字符串多余的存储空间,同上,注意出现的类型下降相关的代码
sdstrim 删除掉字符串2头的在指定字符集合内的字符
sdsrange 将字符串内容设置为指定区间内的字符,注意其中copy字符串时,使用的是memmove函数
sdscatlen 为当前字符串增加新的内容。
sdssplitlen 将字符串按照分隔符,切割成若干个sds字符串。
sdssplitargs 将命令行字符串的参数解析出来,将其设置为一个sds数组的形式,返回给调用者。

此外还有一些函数,如join,map,fmt print等操作,此处不再一一指出。












 

 

转载于:https://www.cnblogs.com/crane2000/p/redis_base_data_struct_sdsstring_analysis.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值