字符串对象¶
字符串对象的编码可以是 int
、 raw
或者 embstr
。
如果一个字符串对象保存的是整数值,
并且这个整数值可以用 long
类型来表示,
那么字符串对象会将整数值保存在字符串对象结构的 ptr
属性里面(将 void*
转换成 long
),
并将字符串对象的编码设置为 int
。
举个例子,
如果我们执行以下 SET 命令,
那么服务器将创建一个如图 8-1 所示的 int
编码的字符串对象作为 number
键的值:
redis> SET number 10086
OK
redis> OBJECT ENCODING number
"int"
![digraph {
label = "\n 图 8-1 int 编码的字符串对象";
rankdir = LR;
node [shape = record];
redisObject [label = " redisObject | type \n REDIS_STRING | encoding \n REDIS_ENCODING_INT | <ptr> ptr | ... "];
node [shape = plaintext];
number [label = "10086"]
redisObject:ptr -> number;
}](../../_images/graphviz-9b10f3b4de5f3e050ae38fc8e036869d37f38d2b.png)
如果字符串对象保存的是一个字符串值,
并且这个字符串值的长度大于 39
字节,
那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,
并将对象的编码设置为 raw
。
举个例子,
如果我们执行以下命令,
那么服务器将创建一个如图 8-2 所示的 raw
编码的字符串对象作为 story
键的值:
redis> SET story "Long, long, long ago there lived a king ..."
OK
redis> STRLEN story
(integer) 43
redis> OBJECT ENCODING story
"raw"
![digraph {
label = "\n 图 8-2 raw 编码的字符串对象";
rankdir = LR;
node [shape = record];
redisObject [label = " redisObject | type \n REDIS_STRING | encoding \n REDIS_ENCODING_RAW | <ptr> ptr | ... "];
sdshdr [label = " <head> sdshdr | free \n 0 | len \n 43 | <buf> buf"];
buf [label = " { 'L' | 'o' | 'n' | 'g' | ... | 'k' | 'i' | 'n' | 'g' | ' ' | '.' | '.' | '.' | '\\0' } " ];
//
redisObject:ptr -> sdshdr:head;
sdshdr:buf -> buf;
}](../../_images/graphviz-25c397540df6b2f776b820a30866bba106bccb7b.png)
如果字符串对象保存的是一个字符串值,
并且这个字符串值的长度小于等于 39
字节,
那么字符串对象将使用 embstr
编码的方式来保存这个字符串值。
embstr
编码是专门用于保存短字符串的一种优化编码方式,
这种编码和 raw
编码一样,
都使用 redisObject
结构和 sdshdr
结构来表示字符串对象,
但 raw
编码会调用两次内存分配函数来分别创建 redisObject
结构和 sdshdr
结构,
而 embstr
编码则通过调用一次内存分配函数来分配一块连续的空间,
空间中依次包含 redisObject
和 sdshdr
两个结构,
如图 8-3 所示。
![digraph {
label = "\n 图 8-3 embstr 编码创建的内存块结构";
node [shape = record];
embstr [ label = " { redisObject | { type | encoding | <ptr> ptr | ... } } | { sdshdr | { free | len | <buf> buf }} " ];
}](../../_images/graphviz-7baad9e9ebbcdcb592a40ee4e7ddbbe758f252eb.png)
embstr
编码的字符串对象在执行命令时,
产生的效果和 raw
编码的字符串对象执行命令时产生的效果是相同的,
但使用 embstr
编码的字符串对象来保存短字符串值有以下好处:
embstr
编码将创建字符串对象所需的内存分配次数从raw
编码的两次降低为一次。释放
embstr
编码的字符串对象只需要调用一次内存释放函数, 而释放raw
编码的字符串对象需要调用两次内存释放函数。因为
embstr
编码的字符串对象的所有数据都保存在一块连续的内存里面, 所以这种编码的字符串对象比起raw
编码的字符串对象能够更好地利用缓存带来的优势。
作为例子,
以下命令创建了一个 embstr
编码的字符串对象作为 msg
键的值,
值对象的样子如图 8-4 所示:
redis> SET msg "hello"
OK
redis> OBJECT ENCODING msg
"embstr"
![digraph {
label = "\n 图 8-4 embstr 编码的字符串对象";
node [shape = record];
embstr [ label = " { redisObject | { type \n REDIS_STRING | encoding \n REDIS_ENCODING_EMBSTR | <ptr> ptr | ... } } | { sdshdr | { free \n 0 | len \n 5 | { buf | { <buf> 'h' | 'e' | 'l' | 'l' | 'o' | '\\0'}} }} " ];
embstr:ptr -> embstr:buf;
}](../../_images/graphviz-a0882d2a335237338f8395b6c8e4eb7bd8ee3df3.png)
最后要说的是,
可以用 long double
类型表示的浮点数在 Redis 中也是作为字符串值来保存的:
如果我们要保存一个浮点数到字符串对象里面,
那么程序会先将这个浮点数转换成字符串值,
然后再保存起转换所得的字符串值。
举个例子,
执行以下代码将创建一个包含 3.14
的字符串表示 "3.14"
的字符串对象:
redis> SET pi 3.14
OK
redis> OBJECT ENCODING pi
"embstr"
在有需要的时候, 程序会将保存在字符串对象里面的字符串值转换回浮点数值, 执行某些操作, 然后再将执行操作所得的浮点数值转换回字符串值, 并继续保存在字符串对象里面。
举个例子, 如果我们执行以下代码的话:
redis> INCRBYFLOAT pi 2.0
"5.14"
redis> OBJECT ENCODING pi
"embstr"
那么程序首先会取出字符串对象里面保存的字符串值 "3.14"
,
将它转换回浮点数值 3.14
,
然后把 3.14
和 2.0
相加得出的值 5.14
转换成字符串 "5.14"
,
并将这个 "5.14"
保存到字符串对象里面。
表 8-6 总结并列出了字符串对象保存各种不同类型的值所使用的编码方式。
表 8-6 字符串对象保存各类型值的编码方式
值 |
编码 |
---|---|
可以用 |
|
可以用 |
|
字符串值,
或者因为长度太大而没办法用 |
|
编码的转换¶
int
编码的字符串对象和 embstr
编码的字符串对象在条件满足的情况下,
会被转换为 raw
编码的字符串对象。
对于 int
编码的字符串对象来说,
如果我们向对象执行了一些命令,
使得这个对象保存的不再是整数值,
而是一个字符串值,
那么字符串对象的编码将从 int
变为 raw
。
在下面的示例中,
我们通过 APPEND 命令,
向一个保存整数值的字符串对象追加了一个字符串值,
因为追加操作只能对字符串值执行,
所以程序会先将之前保存的整数值 10086
转换为字符串值 "10086"
,
然后再执行追加操作,
操作的执行结果就是一个 raw
编码的、保存了字符串值的字符串对象:
redis> SET number 10086
OK
redis> OBJECT ENCODING number
"int"
redis> APPEND number " is a good number!"
(integer) 23
redis> GET number
"10086 is a good number!"
redis> OBJECT ENCODING number
"raw"
另外,
因为 Redis 没有为 embstr
编码的字符串对象编写任何相应的修改程序
(只有 int
编码的字符串对象和 raw
编码的字符串对象有这些程序),
所以 embstr
编码的字符串对象实际上是只读的:
当我们对 embstr
编码的字符串对象执行任何修改命令时,
程序会先将对象的编码从 embstr
转换成 raw
,
然后再执行修改命令;
因为这个原因,
embstr
编码的字符串对象在执行修改命令之后,
总会变成一个 raw
编码的字符串对象。
以下代码展示了一个 embstr
编码的字符串对象在执行 APPEND 命令之后,
对象的编码从 embstr
变为 raw
的例子:
redis> SET msg "hello world"
OK
redis> OBJECT ENCODING msg
"embstr"
redis> APPEND msg " again!"
(integer) 18
redis> OBJECT ENCODING msg
"raw"
字符串命令的实现¶
因为字符串键的值为字符串对象, 所以用于字符串键的所有命令都是针对字符串对象来构建的, 表 8-7 列举了其中一部分字符串命令, 以及这些命令在不同编码的字符串对象下的实现方法。
表 8-7 字符串命令的实现
命令 |
|
|
|
---|---|---|---|
SET |
使用 |
使用 |
使用 |
GET |
拷贝对象所保存的整数值, 将这个拷贝转换成字符串值, 然后向客户端返回这个字符串值。 |
直接向客户端返回字符串值。 |
直接向客户端返回字符串值。 |
APPEND |
将对象转换成 |
将对象转换成 |
调用 |
INCRBYFLOAT |
取出整数值并将其转换成
|
取出字符串值并尝试将其转换成
|
取出字符串值并尝试将其转换成
|
INCRBY |
对整数值进行加法计算, 得出的计算结果会作为整数被保存起来。 |
|
|
DECRBY |
对整数值进行减法计算, 得出的计算结果会作为整数被保存起来。 |
|
|
STRLEN |
拷贝对象所保存的整数值, 将这个拷贝转换成字符串值, 计算并返回这个字符串值的长度。 |
调用 |
调用 |
SETRANGE |
将对象转换成 |
将对象转换成 |
将字符串特定索引上的值设置为给定的字符。 |
GETRANGE |
拷贝对象所保存的整数值, 将这个拷贝转换成字符串值, 然后取出并返回字符串指定索引上的字符。 |
直接取出并返回字符串指定索引上的字符。 |
直接取出并返回字符串指定索引上的字符。 |