redis字符串键(数据结构篇)

字符串(string)

储存文字、数字或者二进制数据。
Redis 中最简单的数据结构,它既可以储存文字(比如 “hello world”),又可以储存数字(比如整数10086 和浮点数 3.14),还可以储存二进制数据(比如 10010100)。
Redis 为这几种类型的值分别设置了相应的操作命令,让用户可以针对不同的值做不同的处理。
在这里插入图片描述

基本操作

为字符串键设置值、获取字符串键的值、获取字符串值的长度,等等。

为字符串键设置值

SET key value
将字符串键 key 的值设置为 value ,命令返回 OK 表示设置成功。

如果字符串键 key 已经存在,那么用新值覆盖原来的旧值。 复杂度为 O(1) 。

 redis> SET msg "hello world"
 OK
 redis> SET msg "goodbye" # 覆盖原来的值 "hello world"
 OK
SET key value [NX|XX]

SET 命令还支持可选的 NX 选项和 XX 选项:
• 如果给定了 NX 选项,那么命令仅在键 key 不存在的情况下,才进行设置操作;如果键 key 已经存在,那么 SET … NX 命令不做动作(不会覆盖旧值)。
• 如果给定了 XX 选项,那么命令仅在键 key 已经存在的情况下,才进行设置操作;如果键 key 不存在,那么 SET … XX 命令不做动作(一定会覆盖旧值)。
在给定 NX 选项和 XX 选项的情况下,SET 命令在设置成功时返回 OK ,设置失败时返回 nil 。

 redis> SET nx-str "this will fail" XX # 键不存在,指定 XX 选项导致设置失败
 (nil)
 redis> SET nx-str "this will success" NX # 键不存在,所以指定 NX 选项是可行的
 OK
 redis> SET nx-str "this will fail" NX # 键已经存在,指定 NX 选项导致设置失败
 (nil)
 redis> SET nx-str "this will success again!" XX # 键已经存在,指定 XX 选项是可行的
 OK
获取字符串的值

GET key
返回字符串键 key 储存的值。 复杂度为 O(1) 。

 redis> SET msg "hello world"
 OK
 redis> GET msg
 hello world
 redis> SET number 10086
 OK
 redis> GET number
 10086
示例:使用 Redis 来进行缓存

我们可以使用 Redis 来缓存一些经常会被用到、或者需要耗 费大量资源的内容,通过将这些内容放到Redis 里面(也即是内存里面),程序可以以极快的速度取得 这些内容。举个例子,对于一个网站来说,如果某个页面经常会被访问到,或者创建页面时耗费的资源比较多(比如需要多次访问数据库、生成时间比较长,等等),那么我们可以使用 Redis 将这个页面缓存起来,减轻网站的负担,降低网站的延迟值。

 @app.route("/")
 def index():
 cached_content = cache.get('index') # 尝试从缓存里面获取被缓存的页面
 if cached_content: # 缓存存在,直接返回页面
 return cached_content
 else:
 content = fetch_and_create_index() # 页面没有被缓存,访问数据库并重新生成页面
 cache.put('index', content) # 缓存页面,方便下次取出
 return content # 返回页面
缓存程序的 API 及其实现

在这里插入图片描述
缓存程序的具体实现请参考 cache.py 。
之后还会实现根据时间自动失效的缓存。

//cache.py
# coding: utf-8

class Cache:

    def __init__(self, client):
        self.client = client

    def put(self, name, content):
        self.client.set(name, content)

    def get(self, name):
        return self.client.get(name)
仅在键不存在的情况下进行设置

SETNX key value
仅在键 key 不存在的情况下,将键 key 的值设置为 value ,效果和 SET key value NX 一样。NX 的意思为“Not eXists”(不存在)。键不存在并且设置成功时,命令返回 1 ;因为键已经存在而导致设置失败时,命令返回 0 。 复杂度为 O(1) 。

 redis> SETNX new-key "i am a new key!"
 1
 redis> SETNX new-key "another new key here!" # 键已经存在,设置失败
 0
 redis> GET new-key # 键的值没有改变
 i am a new key!
同时设置或获取多个字符串键的值

在这里插入图片描述

示例:设置或获取个人信息

很多网站都会给你一个地方,填写自己的个人信息、联系信息、个人简介等等,比如右图就是某个网站上的个人信息 设置页面。通过将每项信息储存在一个字符串键里面(比如电子邮件在 huangz::email 键、个人网站在 huangz::homepage 键、公司在huangz::company 键,等等),我们可以通过 调用 MSET 来一次性设置多个项,并使用MGET 来一次性获取多个项的信息。MSET huangz::email"[email protected]" huangz::homepage “http://huangz.me/” huangz::company “FakeCompany” huangz::position"Programmer" huangz::location “广东” huangz::sign "time waits for no one"MGET huangz::email huangz::homepage huangz::company huangz::position …
在这里插入图片描述

键的命名

因为 Redis 的数据库不能出现两个同名的键,所以我们通常会使用 field1::field2::field3 这样的格式来区分同一类型的多个字符串键。 举个例子,像前面储存个人信息例子,因为网站里面不可能只有 huangz 一个用户,所以我们不能用email 键来直接储存 huangz 的邮件地址,而是使用 huangz::email ,这样 huangz 的邮件地址就不会和其他用户的邮件地址发生冲突 —— 比如用户名为 peter 的用户可以将它的邮件地址储存到peter::email 键,而用户名为 jack 的用户也可以将它的邮件地址储存到 jack::email 键,大家各不相关,互不影响。一些更为复杂的键名例子: user::10086::info ,ID 为 10086 的用户的信息; news::sport::cache ,新闻网站体育分类的缓存; message::123321::content ,ID 为 123321 的消息的内容。:: 是比较常用的分割符,你也可以 选择自己喜欢的其他分割符来命名键,比如斜线 huangz/email 、竖线 huangz|email 、或者面向对象风格的 huangz.email 。

一次设置多个不存在的键

MSETNX key value [key value …]
只有在所有给定键都不存在的情况下, MSETNX 会为所有给定键设置值,效果和同时执行多个SETNX 一样。如果给定的键至少有一个是存在的,那么 MSETNX 将不执行任何设置操作。返回 1 表示设置成功,返回 0 表示设置失败。复杂度为 O(N) , N 为给定的键数量。

redis> MSETNX nx-1 "hello" nx-2 "world" nx-3 "good luck"
 1
 redis> SET ex-key "bad key here"
 OK
 redis> MSETNX nx-4 "apple" nx-5 "banana" ex-key "cherry" nx-6 "durian"
 0

因为 ex-key 键已经存在,所以第二个 MSETNX 会执行失败,所有键都不会被设置。

设置新值并返回旧值

GETSET key new-value
将字符串键的值设置为 new-value ,并返回字符串键在设置新值之前储存的旧值(old value)。
复杂度为 O(1) 。

 redis> SET getset-str "i'm old value" # 先给字符串键设置一个值
 OK
 redis> GETSET getset-str "i'm new value" # 更新字符串键的值,并返回之前储存的旧值
 i'm old value
 redis> GET getset-str # 确认一下,新值已被设置
 i'm new value
用伪代码表示 GETSET 的定义
def GETSET(key, new-value):
 old-value = GET(key) # 记录旧值
 SET(key, new-value) # 设置新值
 return old-value # 返回旧值
追加内容到字符串末尾

APPEND key value
将值 value 推入到字符串键 key 已储存内容的末尾。O(N), 其中 N 为被推入值的长度。

 redis> SET myPhone "nokia"
 OK
 redis> APPEND myPhone "-1110"
 (integer) 10
 redis> GET myPhone
 "nokia-1110

在这里插入图片描述

返回值的长度

STRLEN key
返回字符串键 key 储存的值的长度。
因为 Redis 会记录每个字符串值的长度,所以获取该值的复杂度为 O(1) 。

 redis> SET msg "hello"
 OK
 redis> STRLEN msg
 (integer) 5
 redis> APPEND msg " world"
 (integer) 11
 redis> STRLEN msg
 (integer) 11

在这里插入图片描述

索引和范围

字符串的正数索引和负数索引,范围取值,范围设置。

索引

字符串的索引(index)以 0 为开始,从字符串的开头向字符串的结尾依次递增,字符串第一个字符的索引为 0 ,字符串最后一个字符的索引 为 N-1 ,其中 N 为字符串的长度。除了(正数)索引之外,字符串 还有负数索引:负数索引以 -1 为开始,从字符串的结尾向字符串的开头依次递减,字符串的最后一个字符的索引 为 -N ,其中 N 为字符串的长度。
在这里插入图片描述

范围设置

SETRANGE key index value
从索引 index 开始,用 value 覆写(overwrite)给定键 key 所储存的字符串值。只接受正数索引。命令返回覆写之后,字符串 值的长度。复杂度为 O(N), N 为 value 的长度。

 redis> SET msg "hello"
 OK
 redis> SETRANGE msg 1 "appy"
 (integer) 5
 redis> GET msg
 "happy

在这里插入图片描述

范围取值

GETRANGE key start end
返回键 key 储存的字符串值中,位于 start 和 end 两个索引之间的内容(闭区间,start 和 end 会被包括在内)。和 SETRANGE 只接受正数索引不同, GETRANGE 的索引可以是正数或者 负数。复杂度为 O(N) , N 为被选中内容的长度。

 redis> SET msg "hello world"
 OK
 redis> GETRANGE msg 0 4
 "hello"
 redis> GETRANGE msg -5 -1
 "world

在这里插入图片描述

数字操作

增加或者减少字符串键储存的数字值。

设置和获取数字

只要储存在字符串键里面的值可以被解释为 64 位整数,或者 IEEE-754 标准的 64 位浮点数,那么用户就可以对这个字符串键执行针对数字值的命令。
在这里插入图片描述

增加或者减少数字的值

对于一个保存着数字的字符串 键 key ,我们可以使用 INCRBY 命令来增加它的值,或者使用 DECRBY 命令来减少它的值。
在这里插入图片描述
如果执行 INCRBY 或者 DECRBY 时,键 key 不存在,那么命令会将 键 key 的 值初始化为 0 ,然后再执行增加或者减少操作。

INCRBY / DECRBY 示例
 redis> INCRBY num 100 # 键 num 不存在,命令先将 num 的值初始化为 0 ,
 (integer) 100 # 然后再执行加 100 操作
redis> INCRBY num 25 # 将值再加上 25
(integer) 125
redis> DECRBY num 10 # 将值减少 10
(integer) 115
redis> DECRBY num 50 # 将值减少 50
(integer) 65
增一和减一

因为针对数字值的增一和减一操作非常常 见,所以 Redis 特别为这两个操作创建了 INCR 命令和DECR 命令。
在这里插入图片描述

redis> SET num 10
OK
redis> INCR num
(integer) 11
redis> DECR num
(integer) 10
示例:计数器(counter)

在这里插入图片描述
很多网站都使用了计数器来记录页面被访问的次数。
每当用户访问页面时,程序首先将页面访问计数器的值增一,然后将计数器当前的值返回给用户观看,以便用户通过页面的访问次数来判断页面内容的受关注程度。使用字符串键以及 INCR 、GET 等命令,我们也可以实现这样的计数器。

计数器 API 及其实现

在这里插入图片描述

c = Counter('page-counter', redis_client) # 创建一个名为 page-counter 的计数器
c.incr() # => 1
c.incr() # => 2

计数器实现的完整源码请查看 counter.py 文件。

//counter.py
# encoding: utf-8

class Counter:

    def __init__(self, key, client):
        self.key = key
        self.client = client

    def incr(self, n=1):
        counter = self.client.incr(self.key, n)
        return int(counter)

    def decr(self, n=1):
        counter = self.client.decr(self.key, n)
        return int(counter)

    def reset(self, n=0):
        counter = self.client.getset(self.key, n)
        if counter is None:
            counter = 0
        return int(counter)

    def get(self):
        counter = self.client.get(self.key)
        if counter is None:
示例:id 生成器

很多网站在创建新条目的时候,都会使用 id 生成器来为条目创建唯一标识符。
举个例子,对于一个论坛来说,每注册一个新用户,论坛都会为这个新用户创建一个用户 id ,比如12345 ,然后访问 /user/12345 就可以看到这个用户的个人页面。又比如说,当论坛里的用户创建一个新帖子的时候,论坛都会为这个新帖子创建一个帖子 id ,比如10086 ,然后访问 /topic/10086 就可以看到这个帖子的内容。被创建的 id 通常都是连续的,比如说,如果最新创建的 id 为 1003 ,那么下一个生成的 id 就会是1004 ,再下一个 id 就是 1005 ,以此类推。
在这里插入图片描述

id 生成器 API 及其实现

在这里插入图片描述

generator = IdGenerator('user-id', redis_client) # 创建一个用户 id 生成器
generator.init(10000) # 保留前一万个 id
generator.gen() # => 10001
generator.gen() # => 10002

id 生成器的源代码可以在 id_generator.py 找到。

//id_generator.py
# coding: utf-8

class IdGenerator:

    def __init__(self, key, client):
        self.key = key
        self.client = client

    def init(self, n):
        self.client.set(self.key, n)

    def gen(self):
        new_id = self.client.incr(self.key)
        return int(new_id)
浮点数的自增和自减

INCRBYFLOAT key increment
为字符串键 key 储存的值加上浮点数增量 increment ,命令返回操作执行之后,键 key 的值。没有相应的 DECRBYFLOAT ,但可以通过给定负值来达到 DECRBYFLOAT 的效果。复杂度为 O(1) 。

 redis> SET num 10
 OK
 redis> INCRBYFLOAT num 3.14
 "13.14"
 redis> INCRBYFLOAT num -2.04 # 通过传递负值来达到做减法的效果
 "11.1"
注意事项

即使字符串键储存的是数字值,它也可以执行 APPEND、STRLEN、SETRANGE 和 GETRANGE 。当用户针对一个数字值执行这些命令的时候,Redis 会先将数字值转换为字符串,然后再执行命令。

 redis> SET number 123
 OK
 redis> STRLEN number # 转换为 "123" ,然后计算这个字符串的长度
 3
 redis > APPEND number 456 # 转换为 "123" ,然后与 "456" 进行拼接
 6
 redis> GET number
 123456

二进制数据操作

设置和获取字符串储存的二进制数据,执行二进制位运算。

设置和获取二进制数据

SET 、GET 、SETNX、 APPEND 等命令同样可以用于设置二进制数据。

# 因为 Redis 自带的客户端 redis-cli 没办法方便的设置二进制数据
# 所以这里使用 Python 客户端来进行
 >>> import redis
 >>> r = redis.Redis()
 >>> r.set('bits', 0b10010100) # 将字符串键 bits 的值设置为二进制 10010100
 True
 >>> bin(int(r.get('bits'))) # 获取字符串键 bits 储存的二进制值(需要进行转换)
 '0b10010100'
 >>> r.append('bits', 0b111) # 将 0b111 (也即是十进制的 7)推入到 bits 已有二进制位的末尾
 4L
 >>> bin(int(r.get('bits'))) # 推入之前的值为 0b10010100 = 148
 '0b10111001111' # 推入之后的值为 0b10111001111 = 1487
二进制位的索引

和储存文字时一样,字符串键在储存二进制位时,索引也是从 0 开始的。但是和储存文字时,索引从左到右依次递增不同,当字符串键储存的是二进制位时,二进制位的索引会从左到右依次递减。在这里插入图片描述

设置二进制位的值

SETBIT key index value
将给定索引上的二进制位的值设置为 value ,命令返回被设置的位原来储存的旧值。 复杂度为 O(1) 。

redis> SETBIT bits 2 1
 (integer) 0

在这里插入图片描述

获取二进制位的值

GETBIT key index
返回给定索引上的二进制位的值。 复杂度为 O(1) 。

 redis> GETBIT bits 7
 (integer) 1
 redis> GETBIT bits 6
 (integer) 0
 redis> GETBIT bits 4
 (integer) 1

在这里插入图片描述

计算值为 1 的二进制位的数量

BITCOUNT key [start] [end]
计算并返回字符串键储存的值中,被设置为 1 的二进制位的数量。
一般情况下,给定的整个字符串键都会进行计数操作,但通过指定额外的 start 或 end 参数,可以让计数只在特定索引范围的位上进行。
start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推。复杂度为 O(N) ,其中 N 为被计算二进制位的数量。

BITCOUNT 示例

在这里插入图片描述

带有 start 和 end 参数的BITCOUNT 示例

在这里插入图片描述

二进制位运算

BITOP operation destkey key [key …]
对一个或多个保存二进制位的字符串键执行位元操作,并将结果保存到 destkey 上。operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种:
在这里插入图片描述
除了 NOT 操作之外,其他操作都可以接受一个或以上数量的 key 作为输入。复杂度为 O(N) , N 为进行计算的二进制位数量的总和。命令的返回值为计算所得结果的字节长度,相当于对 destkey 执行 STRLEN 。

BITOP 示例

假设现在 b1 键储存了二进制 01001101 ,而 b2 键储存了二进制 10110101 。

redis> BITOP AND b1-and-b2 b1 b2 # b1-and-b2 = 00000101
(integer) 1
redis> BITOP OR b1-or-b2 b1 b2 # b1-or-b2 = 11111101
(integer) 1
redis> BITOP XOR b1-xor-b2 b1 b2 # b1-xor-b2 = 11111000
(integer) 1
redis> BITOP NOT not-b1 b1 # not-b1 = 10110010
(integer) 1
示例:实现在线人数统计

一些网站具备了在线人数统计功能,通过这个功能可以看到一段时间以内(比如这个小时,或者这一天),曾经登录过这个网站的会员人数。
在这里插入图片描述
某网站的在线人数统计结果,显示目前有 289 个会员在线。 通过使用字符串键以及二进制数据处理命令,我们也可以构建一个高效并且节约内存的在线人数统计实现。

在用户 id 和位索引之间进行关联

之前说过,字符串键储存的每个二进制位都有与之对应的索引,比如对于一个 8 位长的二进制值来说,它的各个二进制位的索引值为 0 至 7 。 因为通常网站的每个会员都有一个自己的数字 id ,比如 peter的 id 可能是 3 ,而 jack 的 id 可能是 5 ,所以我们可以在用户id 和二进制位的索引之间进行关联: • 如果 id 为 N 的用户在线,我们就将索引为 N 的二进制位的值设置为 1 。 • 如果索引为 N 的二进制位的值为 0 ,这表示 id 为 N 用户不在线。 • 使用 BITCOUNT 可以统计有多少个用户在线。 • 通过为每段时间分别储存一个二进制值,我们就可以为每 段时间都记录在线用户的数量。(每小时创建一个键或者每天创建一个键,诸如此类。)
在这里插入图片描述

在线用户统计的 API 及其实现

在这里插入图片描述

count = OnlineCount(‘2014-8-3 10a.m.’) # 记录 2014 年 8 月 3 日上午 10 点的在线用户数量
count.include(4) # 将 id 为 4 的用户设置为在线
count.include(5) # 将 id 为 5 的用户设置为在线
count.include(7) # 将 id 为 7 的用户设置为在线
count.result() # 返回 3 ,表示有三个用户在线 

在线用户统计程序的完整实现代码可以在 online_count.py 查看。

//online_count.py
# encoding: utf-8

class OnlineCount:

    def __init__(self, when, client):
        self.when = when
        self.client = client

    def include(self, user_id):
        return self.client.setbit(self.when, user_id, 1)

    def result(self):
        return self.client.bitcount(self.when)
关于用户在线统计的更多信息

目前这个实现的优点:
• 将用户设置为在线的速度非常快, O(1) 。 • 即使用户数量非常大,占用的内存也不多:记录一百万用户仅需一百万位,也即是 0.125MB;记录一千万用户仅需一千万位,也即是 1.25 MB 。 • 可以在现有程序的基础上,做进一步的操作。举个例子,我们可以使用 BITOP AND 命令,将多个在线记录作为输入,计算出全勤用户的数量(全勤指的是,用户在所有输入的在线统计记录中,都显示为在线)。目前这个实现的缺点:
• 每次进行统计的复杂度为 O(N) 。 • 没办法轻易地获取所有在线用户的名单,只能遍历整个二进制值,复杂度为 O(N) ,其中 N 为二进制位数量。
进一步的优化:
• 用户量不大并且需要获取在线用户名单的话,可以使用之后介绍的集合数据结构来实现。
• 不需要获取在线用户名单,并且不需要精确的在线统计数量,
可以使用之后介绍的 HyperLogLog 来实现。

照片分享网站/拍照应用

在这里插入图片描述

示例:使用 Redis 缓存热门图片

图片网站要储存大量的图片(通常放在硬盘里面),而少部分热门的图片会被经常地访问到。为了加快网站获取热门图片的速度,我们可以利用 Redis 能够储存二进制数据这一特性,使用之前构建的缓存程序来缓存图片网站中的热门图片。

cache = Cache(redis_client) # 设置缓存的客户端
file = open('redis-logo.jpg', 'r') # 打开文件
data = file.read() # 读取文件数据
file.close() # 关闭文件
cache.put('redis-logo', data) # 以 redis-logo 为名字,将图片缓存起来
cache.get('redis-logo') # 取出 redis-logo 图片的数据

储存中文时的注意事项

STRLEN、SETRANGE 和 GETRANGE 不适用于中文

注意事项

一个英文字符只需要使用 单个字节来储存,而一个中文字符却需要使用多个字 节来储存。
在这里插入图片描述
STRLEN、SETRANGE 和 GETRANGE 都是为英文设置的,它们只会在字符为单个字节的情况下正常工作,而一旦我们储存的是类似中文这样的多字节字符,那么这三个命令就不再适用了。

STRLEN 示例
$ redis-cli --raw # 在 redis-cli 中使用中文时,必须打开 --raw 选项,才能正常显示中文
 redis> SET msg "世界你好" # 设置四个中文字符
 OK
 redis> GET msg # 储存中文没有问题
 世界你好
 redis> STRLEN msg # 这里 STRLEN 显示了“世界你好”的字节长度为 12 字节
 12 # 但我们真正想知道的是 msg 键里面包含多少个字符

在这里插入图片描述

SETRANGE 和 GETRANGE 示例

SETRANGE 和 GETRANGE 的情况也是类似的:因为这两个命令所使用的索引是根据字 节而不是字符来编排的,所以调用 SETRANGE 或者 GETRANGE 来处理中文,得不到我们想要的结果。

redis> SET msg "世界你好"
 OK
 redis> GETRANGE msg 2 3 # 试图获取 "你好"
 �
 redis> SETRANGE msg 2 "欢迎你" # 试图构建 "世界欢迎你",其中"欢迎你"为 9 字节长
 12
 redis> GET msg
 ��欢迎你�

在这里插入图片描述

结论

不要使用 STRLEN、SETRANGE 和 GETRANGE 来处理中文。
例外情况:如果你想知道被储存的中文包含多少个字节,那么可以使用 STRLEN 。

复习

每个字符串键可以保存一个值,这个值可以是文字、整数、浮点数或者二 进制数据。
不要使用 STRLEN、SETRANGE 和 GETRANGE 来处理中文。
在这里插入图片描述

发布了217 篇原创文章 · 获赞 125 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_39885372/article/details/104231131