SETRANGE:对字符串值的指定索引范围进行设置

通过使用 SETRANGE 命令,用户可以将字符串键的值从索引 index 开始的部分替换为指定的新内容,被替换内容的长度取决于新内容的长度:

SETRANGE key index substitute

SETRANGE 命令在执行完设置操作之后,会返回字符串值当前的长度作为结果。

例如,我们可以通过执行以下命令,将 message 键的值从原来的 "hello world" 修改为 "hello Redis":

redis> GET message
"hello world"
redis> SETRANGE message 6 "Redis"
(integer) 11 -- 字符串值当前的长度为11字节
redis> GET message
"hello Redis"

这个例子中的 SETRANGE 命令会将 message 键的值从索引 6 开始的内容替换为 "Redis",图 2-7 展示了这个命令的执行过程。

image 2025 01 02 15 29 44 439
Figure 1. 图2-7 SETRANGE 命令修改 message 键的过程

自动扩展被修改的字符串

当用户给定的新内容比被替换的内容更长时,SETRANGE 命令就会自动扩展被修改的字符串值,从而确保新内容可以顺利写入。

例如,以下代码就展示了如何通过 SETRANGE 命令,将 message 键的值从原来的 11 字节长修改为 41 字节长:

redis> GET message
"hello Redis"
redis> SETRANGE message 5 ", this is a message send from peter."
(integer) 41
redis> GET message
"hello, this is a message send from peter."

图 2-8 展示了这个 SETRANGE 命令扩展字符串并进行写入的过程。

image 2025 01 02 15 31 32 159
Figure 2. 图2-8 SETRANGE 命令的执行过程示例

在值里面填充空字节

SETRANGE 命令除了会根据用户给定的新内容自动扩展字符串值之外,还会根据用户给定的 index 索引扩展字符串。

当用户给定的 index 索引超出字符串值的长度时,字符串值末尾直到索引 index-1 之间的部分将使用空字节进行填充,换句话说,这些字节的所有二进制位都会被设置为 0 。

举个例子,对于字符串键 greeting 来说:

redis> GET greeting
"hello"

当我们执行以下命令时,SETRANGE 命令会先将字符串值扩展为 15 个字节长,然后将 "hello" 末尾直到索引 9 之间的所有字节都填充为空字节,最后再将索引 10 到索引 14 的内容设置为 "world"。图2-9展示了这个扩展、填充、最后设置的过程。

redis> SETRANGE greeting 10 "world"
(integer) 15
image 2025 01 02 15 33 49 107
Figure 3. 图2-9 SETRANGE greeting 10 "world" 的执行过程

通过执行 GET 命令,我们可以取得 greeting 键在执行 SETRANGE 命令之后 的值:

redis> GET greeting
"hello\x00\x00\x00\x00\x00world"

可以看到,greeting 键的值现在包含了多个 \x00 符号,每个 \x00 符号代表一个空字节。

其它信息

  • 复杂度:O(N),其中 N 为被修改内容的长度。

  • 版本要求:SETRANGE 命令从 Redis 2.2.0 开始可用。

示例:给文章存储程序加上文章长度计数功能和文章预览功能

在前面的内容中,我们使用 MSET、MGET 等命令构建了一个存储文章信息的程序,在学习了 STRLEN 命令和 GETRANGE 命令之后,我们可以给这个文章存储程序加上两个新功能,其中一个是文章长度计数功能,另一个则是文章预览功能。

  • 文章长度计数功能用于显示文章内容的长度,读者可以通过这个长度值来了解一篇文章大概有多长,从而决定是否继续阅读。

  • 文章预览功能则用于显示文章开头的一部分内容,这些内容可以帮助读者快速地了解文章大意,并吸引读者进一步阅读整篇文章。

代码清单 2-4 展示了这两个功能的具体实现代码,其中文章长度计数功能是通过对文章内容执行 STRLEN 命令来实现的,文章预览功能是通过对文章内容执行 GETRANGE 命令来实现的。

代码清单2-4 带有长度计数功能和预览功能的文章存储程序:/string/article.py

from time import time  # time() 函数用于获取当前 Unix 时间戳

class Article:

    # 省略之前展示过的__init__()、create()、update()等方法

    def get_content_len(self):
        """
        返回文章内容的字节长度。
        """
        return self.client.strlen(self.content_key)

    def get_content_preview(self, preview_len):
        """
        返回指定长度的文章预览内容。
        """
        start_index = 0
        end_index = preview_len-1
        return self.client.getrange(self.content_key, start_index, end_index)

get_content_len() 方法的实现非常简单直接,没有什么需要说明的。与此相比,get_content_preview() 方法显得更复杂一些,让我们进行一些分析。

首先,get_content_preview() 方法会接受一个 preview_len 参数,用于记录调用者指定的预览长度。接着程序会根据这个预览长度计算出预览内容的起始索引和结束索引:

start_index = 0
end_index = preview_len-1

因为预览功能要做的就是返回文章内容的前 preview_len 个字节,所以上面这两条赋值语句要做的就是计算并记录文章前 preview_len 个字节所在的索引范围,其中 start_index 的值总是 0,而 end_index 的值则为 preview_len-1。举个例子,假如用户输入的预览长度为 150,那么 start_index 将被赋值为 0,而 end_index 将被赋值为 149。

最后,程序会调用 GETRANGE 命令,根据上面计算出的两个索引,从存储着文章内容的字符串键里面取出指定的预览内容:

self.client.getrange(self.content_key, start_index, end_index)

以下代码展示了如何使用文章长度计数功能以及文章预览功能:

>>> from redis import Redis
>>> from article import Article
>>> client = Redis(decode_responses=True)
>>> article = Article(client, 12345)
>>> title = "Improving map data on GitHub"
>>> content = "You've been able to view and diff geospatial data on GitHub for a while, but now,
in addition to being able to collaborate on the GeoJSON files you upload to GitHub, you can now
more easily contribute to the underlying, shared basemap, that provides your data with context.
"
>>> author = "benbalter"
>>> article.create(title, content, author) # 将一篇比较长的文章存储起来
True
>>> article.get_content_len() # 文章总长273字节
273
>>> article.get_content_preview(100) # 获取文章前100字节的内容
"You've been able to view and diff geospatial data on GitHub for a while, but now, in addition t
o bei"