💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
`Redis`当中每种数据类型都是经过特别设计的,相信大家看完这个系列也会体会到`Redis`设计的精妙之处。字符串在我们眼里是非常简单的一种数据结构了,但是`Redis`却把它优化到了极致,为了节省空间,其通过编码的方式定义了三种不同的存储方式: | 编码属性 | 描述 | object encoding命令返回值 | | --- | --- | --- | | OBJ\_ENCODING\_INT | 使用整数的字符串对象 | int | | OBJ\_ENCODING\_EMBSTR | 使用`embstr`编码实现的字符串对象 | embstr | | OBJ\_ENCODING\_RAW | 使用`raw`编码实现的字符串对象 | raw | * `int`编码:当我们用字符串对象存储的是整型,且能用`8`个字节的`long`类型进行表示(即`2`的`63`次方减`1`),则`Redis`会选择使用`int`编码来存储,此时`redisObject`对象中的`ptr`指针直接替换为`long`类型。我们想想`8`个字节如果用字符串来存储只能存`8`位,也就是千万级别的数字,远远达不到`2`的`63`次方减`1`这个级别,所以如果都是数字,用`long`类型会更节省空间。 * `embstr`编码:当字符串对象中存储的是字符串,且长度小于`44`(`Redis 3.2`版本之前是`39`)时,`Redis`会选择使用`embstr`编码来存储。 * `raw`编码:当字符串对象中存储的是字符串,且长度大于`44`时,`Redis`会选择使用`raw`编码来存储。 讲了半天理论,接下来让我们一起来验证下这些结论。首先启动`Redis`: ~~~bash sudo redis-server /etc/redis/redis.conf redis-cli ~~~ 连接上`Redis`之后依次输入以下命令: ~~~sql set name lonely_wolf type name object encoding name ~~~ 得到如下所示结果: ![](https://img.kancloud.cn/eb/7e/eb7e5fdbec50844b4912ac8b5edb2c6c_698x158.png) 可以发现当前的数据类型就是`string`,普通字符串因为长度小于`44`,所以采用的是`embstr`编码。 再依次输入如下命令: ~~~sql set num 1111111111 type num object encoding num set address aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa //长度 44(注释不要输入) object encoding address set address aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa //长度 45(注释不要输入) object encoding address ~~~ 最后得到如下所示效果: ![](https://img.kancloud.cn/d6/1c/d61c77043e29c79f256f6f7a2b8b795d_744x268.png) 可以发现,当输入纯数字的时候,采用的是`int`编码,而字符串小于等于`44`则为`embstr`,大于`44`则为`raw`编码。 字符串对象中除了上面提到的纯整数和字符串,还可以存储浮点型类型,所以字符串对象可以存储以下三种类型: * 字符串 * 整数 * 浮点数 而当我们的`value`为整数时,还可以使用原子自增命令来实现`value`的自增,这个命令在实际开发过程中非常实用。 * `incr key`:将`key`的值自增`1`。 * `incrby key n`:将`key`的值自增`n`。 ![](https://img.kancloud.cn/c4/83/c4830ac41eb3096afbfc582a19d68cb7_672x173.png) 不过这两个命令只能用在`value`为整数的场景,当`value`不是整数时则会报错。 #### embstr 编码为什么从 39 位修改为 44 位 `embstr`编码中,`redisObject`和`sds`是连续的一块内存空间,这块内存空间`Redis`限制为了`64`个字节,而`redisObject`固定占了 16 字节(上面定义中有标注),`Redis 3.2`版本之前的`sds`占了`8`个字节,再加上字符串末尾`\0`占用了`1`个字节,所以:`64-16-8-1=39`字节。 `Redis 3.2`版本之后`sds`做了优化,对于`embstr`编码会采用`sdshdr8`来存储,而`sdshdr8`占用的空间只有`24`位:`3`字节(len + alloc + flag)+`\0`字符(1 字节),所以最后就剩下了:`64-16-3-1=44`字节。 #### embstr 编码和 raw 编码的区别 `embstr`编码是一种优化的存储方式,其在申请空间的时候因为`redisObject`和`sds`两个对象是一个连续空间,所以**只需要申请`1`次空间(同样的,释放内存也只需要`1`次)**,而`raw`编码因为`redisObject`和`sds`两个对象的空间是不连续的,所以使用的时候**需要申请`2`次空间(同样的,释放内存也需要`2`次)**。但是使用`embstr`编码时,假如需要修改字符串,那么因为`redisObject`和`sds`是在一起的,所以两个对象都需要重新申请空间,为了避免这种情况发生,`embstr`编码的字符串是只读的,不允许修改。 依次输入如下命令来验证一下编码转换: ~~~sql set addr china object encoding addr append addr -beijing //在addr所对应的值后面追加“-beijing”(注释不要输入) get addr object encoding addr ~~~ 得到如下所示结果: ![](https://img.kancloud.cn/ca/0c/ca0c1c0d8481d7187f142f573786d4b9_650x202.png) 上图中的示例我们看到,对一个`embstr`编码的字符串对象进行`append`操作时,长度还没有达到`45`,但是编码已经被修改为`raw`了,这就是因为`embstr`编码是只读的,如果需要对其修改,`Redis`内部会将其修改为`raw`编码之后再操作。同样的,如果是操作`int`编码的字符串之后,导致`long`类型无法存储时(`int`类型不再是整数或者长度超过`2`的`63`次方减`1`时),也会将`int`编码修改为`raw`编码。 PS:**需要注意的是,编码一旦升级(int-->embstr-->raw),即使后期再把字符串修改为符合原编码能存储的格式时,编码也不会回退。**