Redis-动态字符串
1.动态字符串
redis中的字符串,是对c语言中的字符串(以空字符串结尾的字符数组)进行了一层包装,自己定义了一个结构块,名为动态字符串(simple dynamic string ,SDS)的抽象类型,并且将SDS作为redis默认字符串类型(可以表示字符串、整数、位图)
参考:redis的设计与实现
1.1.动态字符串结构
struct SDS{
//记录buf数组中已经使用的字节数量
//等于sds所保存的字符串长度
int8 len; // 1byte
// 记录buf中未使用的字节数量
int8 free; // 1byte
//类型标记 int(long 类型整数), embstr 嵌入式字符串(编码后长度小于 44 字节的字符串) , raw(sds 字符串)
int8 flags; // 1byte
//字符数组,用于保存字符串
byte[] buf[]; // 内联数组,长度为 len
}
1.2.SDS字符串优点
为什么redis性能高,为什么redis要封装字符串.其原因主要在以下几点:
在常数的获取复杂度上:
SDS可以直接返回字符串长度,C语言的需要遍历
缓冲区溢出问题:
在扩容或者修改时,redis每次会检查free的值,从而直接指导buf是否够用.不够会进行扩容处理.而C语言中的字符串则可能出现由于内存已经分配,修改其中的字符串导致内存溢出问题.
内存分配问题
每次C语言处理字符串时,需要重新分配内存,但是redis由于是自定义的结构,相当于可以预支一定的内存.所以可以减少分配次数.在SDS中,如果len小于1MB,则free和len相同,否则redis会每次预支1MB的free
惰性释放
redis的惰性释放由于优化redis中字符串的缩短操作.当字符串缩短时,redis不会立马就释放空间,而是使用free记录可利用的空间,便于以后使用.
1.3.字符串常用关命令
命令 | 描述 |
---|---|
set key value | 存放一个key-value键值对 |
get key | 根据key获取对应的值 |
strlen key | 获取字符串长度 |
getrange key index1 index2 | 获取字符串指定索引范围字符 |
getset key newValue | 获取key的值,并为其设置新的值 |
mset key1 value1 key2 value2 | 批量设置key value值 |
mget key1 key2 | 批量获取key的值 |
setnx key value | 不存在key 就插入 key value ,返回值 1 成功 0 失败 |
setrange key index value | 找到指定的key,使用value值,从index索引处开始替换 |
incr key | 递增,只对值为数字生效 |
incrby key 值 | 指定自增的值 |
decr key | 递减 |
decrby key 值 | 指定递减的值 |
incrbyfloat key 值 | 指定递增的小数,不推荐 可能精度丢失 |
append key 值 | 为key的值追加内容 |
127.0.0.1:6379> set num 123456789
OK
127.0.0.1:6379> get num
"123456789"
127.0.0.1:6379> strlen num
(integer) 9
127.0.0.1:6379> getrange num 0 1
"12"
127.0.0.1:6379> getset num 1234567890
"123456789"
127.0.0.1:6379> get num
"1234567890"
127.0.0.1:6379> mset num1 1 num2 2
OK
127.0.0.1:6379> get num1
"1"
127.0.0.1:6379> mget num1 num2
1) "1"
2) "2"
127.0.0.1:6379> setnx num2 2
(integer) 0
127.0.0.1:6379> setnx num3 3
(integer) 1
127.0.0.1:6379> setrange num 1 000000000
(integer) 10
127.0.0.1:6379> get num
"1000000000"
127.0.0.1:6379> incr num1
(integer) 2
127.0.0.1:6379> incrby num2 10
(integer) 12
127.0.0.1:6379> decr num1
(integer) 1
127.0.0.1:6379> get num1
"1"
127.0.0.1:6379> decrby num2 10
(integer) 2
127.0.0.1:6379> incrbyfloat num1 0.222
"1.222"
127.0.0.1:6379> append num 1
(integer) 11
127.0.0.1:6379> get num
"10000000001"
1.4.字符串类型问题
使用命令 object encoding key可以获取redis中存储的数据的类型.每个数据在redis中都是一个对象.
这个命令的返回值有:
- int long整形
- embstr 嵌入式字符串(redis 5.x 新增的)
- raw redis中的动态字符串
在使用该命令时,会发现一个有意思的问题.
127.0.0.1:6379> set num '1234567890 1234567890 1234567890 1234567890'
OK
127.0.0.1:6379> strlen num
(integer) 43
127.0.0.1:6379> object encoding num
"embstr"
127.0.0.1:6379>set num '1234567890 1234567890 1234567890 1234567890 1'
OK
127.0.0.1:6379> object encoding num
"raw"
注意:当字符串长度为不小于44时,该类型为raw类型
在redis中,每个数据都会当做一个对象处理,而每个对象都会有个头信息.每个对象的头信息一般是==16==个字节
struct RedisObject {
int4 type; // 4bits 类型
int4 encoding; // 4bits
int24 lru; // 24bits 3字节 LRU 信息
int32 refcount; // 4bytes 4字节
void *ptr; // 8bytes,64-bit system 8字节
};
其中:
-
refcount
引用计数,当引用计数为0时,对象就会被销毁,内存会回收.4字节 -
ptr
指针指向对象内容的具体存储位置.8字节
SDS结构体的大小
struct SDS {
int8 capacity; // 1byte
int8 len; // 1byte
int8 flags; // 1byte
byte[] content; // 内联数组,长度为 capacity
}
SDS的大小是: 1+1+1+?,所以一个SDS的大小最小是3个字节.所以存在redis中一个字符串数据大小,最小16+3个字节,19个字节.
而内存分配器等分内存的大小的单位是2的幂次:2/4/8/16/32/64.为了能容纳一个完成的字符串,那么最少分配32个字节空间.如果字符串稍微大一点就是64个字节空间.如果总体超出了 64 字节,Redis 认为它是一个大字符串,不再使用 emdstr 形式存储,而该用 raw 形式。
为什么redis会在超过64个字节时当做raw处理呢.或者说为什么字符串长度为44时,就变为了raw呢?
首先,raw是指redis动态字符串,是radis对c语言原生字符串的一种包装.而原生c语言的字符串,最后一个始终使用\0的字符串结尾,是为了方便使用glibc的字符串函数处理,及便于打印输出.而64-19(所有头占用的)=45
个字符串.字符串又是以\0结尾,所以embstr 最大能容纳的字符串长度就是 44.