11+++
22date = ' 2025-08-19T8:00:00+08:00'
3- draft = true
4- title = ' Introduct Redis'
5- tags = [" Redis" , " Database" ]
3+ draft = false
4+ title = ' Redis String '
5+ tags = [' Redis' , ' Database' ]
66+++
7-
8-
9- # 数据结构与应用
7+ 介绍Redis中的字符串键
108
119## 字符串
1210字符串建是 Redis 最基本的键值对类型, 这种类型的键值对会在数据库中把单独的一个值关联起来, 被关联的键和值可以为文本, 也可以是图片, 视屏, 音频等二进制数据.
1311
1412- SET: 为字符串键设置值 O(1)
15- > SET key value
13+ > SET key value
14+
1615 ```Redis
1716 SET number "10086"
1817 > OK
@@ -31,7 +30,7 @@ tags = ["Redis", "Database"]
3130 ```
3231
3332- GET: 获取字符串键的值 O(1)
34- > GET key
33+ > GET key
3534
3635 ```Redis
3736 GET number
@@ -45,7 +44,7 @@ tags = ["Redis", "Database"]
4544 ```
4645
4746- GETSET: 获取旧值并更新值 O(1)
48- > GETSET key new\_value
47+ > GETSET key new\_ value
4948
5049 ```Redis
5150 GETSET key "123456"
@@ -99,4 +98,242 @@ print(cache.get("daily_bing.jpg")[:20]) # 读取二进制数据的前20字节
9998
10099
101100### 示例: 锁
101+ 锁是一种同步机制, 用于保证一种资源任何时候只能被一个进程使用.
102+ 一个锁的实现通常有获取 (acquire) 和释放 (relase) 这两种操作.
103+
104+ - 获取操作用于获取资源的独占使用权, 任何时候只能有一个进程取得锁, 此时, 取得锁的进程称为锁的持有者.
105+ - 释放操作用于放弃资源的独占使用权, 一般由持有者调用.
106+
107+ ``` Python
108+ from redis import Redis
109+
110+ VALUE_OF_LOCK = " locking"
111+ class Lock :
112+ def __init__ (self , client , key ):
113+ self .client = client
114+ self .key = key
115+ def acquire (self ):
116+ result = self .client.set(self .key, VALUE_OF_LOCK , nx = True )
117+ return result is True
118+ def relase (self ):
119+ return self .client.delete(self .key) == 1
120+
121+ client = Redis(decode_responses = True )
122+ lock = Lock(client, ' test-lock' )
123+ print (" 第一次获取锁:" , lock.acquire())
124+
125+ print (" 第二次获得锁:" , lock.acquire())
126+ print (" 取消锁:" , lock.relase())
127+
128+ print (" 第三次获得锁:" , lock.acquire())
129+ ```
130+
131+ > 第一次获取锁: True
132+ > 第二次获得锁: False
133+ > 取消锁: True
134+ > 第三次获得锁: True
135+
136+ 若要设置锁的时间 ` SET key value NX EX time ` 这样是原子性语法, 删除操作对应命令是 ` DEL key ` , 返回0表示 key 不存在, 返回1~ N表示删除key的数量.
137+
138+ NX 确保锁只有在没有值时加锁成功, 若有值则返回 None, 通过检查 result 是否为 True 来判断是否获得了锁.
139+
140+
141+ - MSET: 一次为多个字符串键设置值 O(N)
142+ > MSET key value [ key value ...]
143+
144+ 同 SET 命令, MSET 执行成功后返回 OK, 并且会用新值覆盖旧值. 由于执行多条 SET 命令要客户端和服务端之间多次进行网络通讯, 因此 MSET 能减少程序执行操作的时间
145+
146+
147+ - MGET: 一次获取多个字符串键的值 O(N)
148+ > MGET key [ key ...]
149+
150+ - MSETNX: 只在键不存在的情况下, 一次为多个键设置值
151+ > MSETNX key value [ key value ...]
152+
153+ 若有任意一次键存在值, 则会取消所有操作, 并返回0. 只有所有键都没有值的时候, 执行才成功, 返回1.
154+
155+ ### 示例: 存储文章信息
156+ 在构建应用程序的时候, 经常会需要批量设计和获取多项信息, 以博客为例:
157+ - 当用户注册博客时, 程序将用户名字、帐号、密码、注册时间等存储起来, 并在登陆时查取这些信息.
158+ - 当编写一篇博客文章时, 就要将博客标题、内容、作者、发表时间存储起来, 并在用户阅读的时候取出这些信息.
159+
160+ 通过 MSET、MSETNX、MGET 命令, 可以实现上面提到的这些批量设置和批量获取操作
161+
162+ ``` Python
163+ from redis import Redis
164+ from datetime import datetime
165+
166+ class Article :
167+ def __init__ (self , client , article_id ):
168+ """ 根据id创建文章id"""
169+ self .client = client
170+ self .id = str (article_id)
171+ self .title_key = " article::" + self .id + " ::title"
172+ self .content_key = " article::" + self .id + " ::content"
173+ self .author_key = " article::" + self .id + " author"
174+ self .create_at_key = " article::" + self .id + datetime.now()
175+
176+ def create (self , title , content , author ):
177+ """ 创建文章"""
178+ article_data = {
179+ self .title_key: title,
180+ self .content_key: content,
181+ self .author_key: author,
182+ self .create_at_key: datetime.now(),
183+ }
184+ return self .client.msetnx(article_data)
185+
186+ def get (self ):
187+ """ 获取文章信息"""
188+ result = self .client.mget(
189+ self .title_key,
190+ self .content_key,
191+ self .author_key,
192+ self .create_at_key,
193+ )
194+ return {
195+ " id" : self .id,
196+ " title" : result[0 ],
197+ " content" : result[1 ],
198+ " author" : result[2 ],
199+ " create_at_key" : result[3 ],
200+ }
201+
202+ def update (self , title = None , content = None , author = None ):
203+ """ 更新文章"""
204+ article_data = {}
205+ if title is not None :
206+ article_data[self .title_key] = title
207+ if content is not None :
208+ article_data[self .content_key] = content
209+ if author is not None :
210+ article_data[self .author_key] = author
211+ return self .client.mset(article_data)
212+
213+ client = Redis(decode_responses = True )
214+ article = Article(client, 10086 )
215+
216+ # 创建文章
217+ print (article.create(" message" , " hello world" , " sx" ))
218+
219+ # 获取文章信息
220+ print (article.get())
221+
222+ # 更新文章作者
223+ print (article.update(author = " join" ))
224+ ```
225+ 上面程序使用了多个字符串键存储文章信息: ` article::<id>::<attribute> `
226+
227+
228+ - STRLEN: 获取字符串的字节长度 O(1)
229+ > STRLEN key
230+
231+ 对于存在的键, 返回字节长度信息. 对于不存在的键, 返回0
232+
233+
234+ - GETRANGE: 获取字符串值指定索引范围上的内容 O(N)
235+ > GETRANGE key start end
236+
237+ ``` Redis
238+ SET message "hello world"
239+ GETRANG message 0 4
240+ > hello
241+
242+ GETRANGE message -5 -1
243+ > world
244+ ```
245+
246+ - SETRANGE: 修改字符串索引范围的值 O(N)
247+ > SETRANGE key index subsitute
248+
249+ ``` Redis
250+ set message "hello world"
251+ SETRANGE message 6 Redis
252+ > (integer) 11
253+
254+ GET message
255+ > hello Redis
256+ ```
257+
258+ 当用户给定的新内容比被替换内容长的时候, SETRANGE 会自动扩展被修改的字符串值
259+ ``` Redis
260+ SETRANGE message 5 ", this is a message"
261+ > (integer) 24
262+
263+ GET message
264+ > "hello, this is a message"
265+ ```
266+
267+ 当用户给出的索引长度超出被替换字符长度时, 字符串末尾到 index-1 之间部分将使用空字符串填充为0
268+ ``` Redis
269+ SET greeting "hello"
270+ SETRANGE greeting 10 "hello"
271+ > (integer) 15
272+
273+ GET greeting
274+ > "hello\x00\x00\x00\x00\x00world"
275+ ```
276+
277+ ### 示例: 给文章存储程序加上文章长度计数功能和文章御览功能给
278+ - 文章长度计数功能: 显示文章长度, 用于估计阅读时长
279+ - 文章预览功能: 显示文章开头一部分内容, 帮助读者快速了解文章
280+
281+ ``` Python
282+ class Article :
283+ ...
284+
285+ def get_content_len (self ):
286+ return self .client.strlen(self .content_key)
287+
288+ def get_content_perview (self , preview_len ):
289+ start_index = 0
290+ end_index = preview_len - 1
291+ return self .client.getrange(self .content, start_index, end_index)
292+ ```
293+
294+
295+ - APPEND: 追加新内容到值的末尾
296+ > APPEND key suffix
297+
298+ 若用户给定的 key 不存在, 则会先将键执行追加操作
299+
300+
301+ ### 示例: 存储日志
302+ 很多程序运行的时候会产生日志, 日志记录了程序的运行状态以及执行过的重要操作.
303+ 若每条日志存储一个键值对, 则会消耗很多资源, 且分散在数据库中, 需要额外的时间查找日志, 这里将不同日志拼接在同一个值里面.
304+ ``` Python
305+ from redis import Redis
306+
307+ LOG_SEPERATOR = " \n "
308+
309+ class Log :
310+ def __init__ (self , client , key ):
311+ self .client = client
312+ self .key = key
313+
314+ def add (self , new_log ):
315+ new_log += LOG_SEPERATOR
316+ self .client.append(self .key, new_log)
317+
318+ def get_all (self ):
319+ all_logs = self .client.get(self .key)
320+ if all_logs is not None :
321+ log_list = all_logs.split(LOG_SEPERATOR )
322+ log_list.remove(" " ) # 删除默认多余的空字符串
323+ return log_list
324+ else :
325+ return []
326+ ```
327+
328+
329+ ----------
330+
331+ 下面介绍使用字符串键存储数字值:
332+
333+ 每当存储一个值到字符串键里面的时候, 有下面两种情况
334+ 1 . C 语言 long long int 类型的整数, 取值范围为 -2^63 ~ 2^63-1 (超出范围会被当成字符串)
335+ 2 . C 语言 long double 类型的浮点数, 正规数公式为 ` value = (-1)^s x (1 + fraction) x 2^(exp-bias) ` , s:1位 为符号位, fraction:52位 为尾数[ 0, 1), exp:11位 指数位, bias:2^10-1=1023 偏移量; 对于次正规数(指数全0)公式为 ` value = (-1)^s x fraction x 2^(1-bias) `
336+ - 最大值: 指数全1, 除了最高位(都为1是无穷大NaN), 尾数全1, ` MAX = (1 + (1 - 2^(-52))) x 2^(2046-1023) `
337+ - 最小正规值: 指数最小正规化=1, 尾数全0, ` Normalized_min = 1 x 2^(1-1023) `
338+ - 次正规数: 当指数全为0表示次正规数, 尾数最小=0...01, ` Subnormal_min = 2^(-1022) x 2^(-52) ` , 次正规数用来表示比最小正规数还小的正数, 但精度降低
102339
0 commit comments