Redis字符串

编程

1,简介

字符串是最基本的key => value 形式

2,语法

redis 127.0.0.1:6379> COMMAND KEY_NAME

例如

redis 127.0.0.1:6379> SET testkey redis

OK

redis 127.0.0.1:6379> GET testkey

"redis"

3,常见命令

序号

命令及描述

1

SET key value [ex=None px=None nx=False xx=False]
设置指定 key 的值
ex,过期时间(秒)
px,过期时间(毫秒)
nx,如果设置为True,则只有name不存在时,当前set操作才执行,值存在,就修改不了,执行没效果
xx,如果设置为True,则只有name存在时,当前set操作才执行,值存在才能修改,值不存在,不会设置新值

2

GET key
获取指定 key 的值。

3

GETRANGE key start end
返回 key 中字符串值的子字符,获取子序列,按照字节来获取。如 字符串 这三个汉字有9个长度,则起始和结束分别为0和8
注意:SETRANGE和GETRANGE所使用的索引都是根据字节而不是字符来编排的,它们都只会在字符为单个字节的情况下才可以正常使用,而我们储存类似中文的多个字节表示的字符时,需要在进入客户端时,使用--raw选项。

4

GETSET key value
将给定 key 的值设为 value ,并返回 key 的旧值(old value)。

5

GETBIT key offset
对 key 所储存的字符串值,获取指定偏移量上的位(bit)。

6

MGET key1 [key2..]
获取所有(一个或多个)给定 key 的值。

7

SETBIT key offset value
对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。

8

SETEX key seconds value
将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。

9

SETNX key value
只有在 key 不存在时设置 key 的值。

10

SETRANGE key offset value
用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。

11

STRLEN key
返回 key 所储存的字符串值的长度。

12

MSET key value [key value ...]
同时设置一个或多个 key-value 对。

13

MSETNX key value [key value ...]
同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。

14

PSETEX key milliseconds value
这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。

15

INCR key
将 key 中储存的数字值增一。

16

INCRBY key increment
将 key 所储存的值加上给定的增量值(increment) 。

17

INCRBYFLOAT key increment
将 key 所储存的值加上给定的浮点增量值(increment) 。

18

DECR key
将 key 中储存的数字值减一。

19

DECRBY key decrement
key 所储存的值减去给定的减量值(decrement) 。

20

APPEND key value
如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。

 

4,数据结构:

Redis中的字符串并不是传统的C语言字符串(即字符数组,以下简称C字符串),而是自己构建了一种 简单动态字符串(Simple Dynamic String,SDS),并将SDS作为Redis的默认字符串表示。在Redis中,C字符串一般只用在无需对字符串值进行修改的地方,比如Redis的启动时的日志。Redis需要的字符串是一个可修改字符长度的字符串,就会用到SDS来表示一个字符串。比如下面这个例子:

127.0.0.1:6379> set a redis

OK

这是一条很简单的命令,将 "redis" 这个字符串与 a 这个键建立映射关系。而 "redis" 在Redis中的表示,就是一个SDS。说了那么久的SDS,那这个SDS到底长什么样呢?我们来看看

sds.h

struct sdshdr {

//记录buf数组中已使用字节的数量

//等于SDS所保存字符串的长度

int len;

//记录buf数组中尚未使用的字节数量

int free;

//字节数组,用于保存字符串

char buf[];

};

图1-1展示了一个SDS的示例:

  • free属性的值为0,表示这个SDS没有任何剩余的可使用字节数
  • len为5,表示这个SDS保存了一个长度为5的字符串
  • buf属性是一个char类型的数组,数组的前五个字节分别保存了"R"、"e"、"d"、"i"、"s"五个字符,而最后一个字节则保存空字符"",代表字符串结束

图1-1

看到这里,可能还有人不明白使用SDS的好处。没关系,我们接下来再看看另一个示例。我们看图1-2,这个SDS和图1-1的SDS不一样,虽然都保存字符串“Redis”。但图1-2中SDS的buf字符数组长度以及free所保存的值都与图1-1的SDS不一样:

 

图1-2

我们都知道,Redis作为一款非关系型的内存数据库,他的值很容易变动。同时我们也知道,C语言中字符数组的长度是无法变动的。如果Redis中使用的字符串是C字符串,而不是SDS,当我们变动一个键所对应的字符串,如果新字符串的长度小于等于原先字符串的长度,那么我们只要替换字符数组上的内容,再把代表字符串结尾的提前(如果新旧字符串长度相等,则空字符串还留在原先的位置)。但如果新字符串的长度大于原先旧字符串的长度,那么很不幸,我们只能重新申请一个能容纳新字符串长度的数组,用于保存新字符串,这对Redis无疑是不利的

于是,Redis在为一个字符串创建一个SDS对象时,通常会申请比字符串长度更长的字节数组(buf),Redis将字符串保存进这个数组,同时在len这个变量保存字符串的长度,再用free这个变量保存buf尚未使用的字节数量。当客户端要求变动一个键所对应的字符串时,如果buf的长度大于新字符串的长度,那么就无需再声明一个新的数组来容纳新字符串了

我们再来看sdshdr这个结构体,这里面有free、len和buf这三个域。那么这个len会不会有些多余?因为free已经记录尚未使用的字节数量了,同时len我们也可以通过:strlen(buf)的方式来计算字符串长度,那么这个len真的是多余的吗?其实不是,如果有使用过Java、Python等这些高级语言的人都有经验,在这些高级语言中,我们可以轻而易举的调用一个函数来获取字符串的长度,而这些高级语言的字符串内部实现,同样也记录了字符串的长度,假设我们要计算一个字符串长度,每次都要调用strlen(buf),这个操作的时间复杂度为O(N),图1-3展示了C程序中计算字符串长度的过程:

图1-3

从图1-3我们可以知道,当通过strlen(buf)计算一个C字符串的长度,游标会遍历到空字符处才停止,而大家在编程中,多多少少在一些业务场景会重复用到某个字符串长度。于是,SDS中,len域的作用就在于此,我们只需要计算一次字符串的长度,当需要用到时直接从len中取,这时的时间复杂度为O(1)

除了获取字符串长度的时间复杂度高之外,C字符串不记录自身长度带来的另一个问题是容易造成缓冲区溢出。C语言中的strcat函数可以拼接两个字符串,具体定义如下:

#include <string.h>

char *strcat(char *dest, const char *src);

strcat函数可以将src字符串中的内容拼接到dest字符串之后,但因为C本身不记录字符串长度,默认认为dest已经分配了足够的内存空间。举个例子,假设程序中有紧邻的两个字符串S1和S2,其中S1保存了字符串“Redis”,而S2保存了字符串“MongoDB”,如图1-4所示

图1-4 在内存中紧邻的两个C字符串

如果程序员没有注意S1的长度,直接执行strcat(S1, "Cluster"),那么势必会覆盖到S2的内存,换言之S2所对应的字符数组的内容会被修改,如图1-5所示

如图1-5   S1的内容溢出到S2所在的位置上

与C字符串不同,SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性,当SDS API需要对SDS进行修改时,API会检查SDS的空间是否满足修改的要求,如果不满足的话,API会自动将SDS的空间扩展至执行所需的大小,然后才执行修改操作

SDS的API里面有一个用于执行拼接操作的sdscat函数,它可以将一个C字符串拼接到给定的SDS所保存的字符串后面,但是在执行拼接操作之前,sdscat会检查给定的SDS的空间是否足够,如果不够的话,sdscat就会扩展SDS的空间,然后才执行拼接操作

例如,我们执行sdscat(s, " Cluster"),其中SDS值s如图1-6所示,那么sdscat检查后发现,目前s的空间并不足以拼接" Cluster",之后,sdscat就会扩展s空间,然后执行拼接" Cluster"操作,拼接完之后的SDS如图1-7所示

 

图1-6   sdscat执行之前的SDS

图1-7   sdscat执行之后的SDS

Redis作为内存数据库,经常被用于速度要求严苛,数据被频繁修改的场合,如果每次修改字符串长度都需要执行一次内存重分配的话,那么光是执行内存重分配的时间就会占去修改字符串所用时间的一大部分,如果这种修改频繁发生的话,可能还会对性能造成影响。为了避免C字符串的缺陷,SDS通过未使用空间解除字符串长度和底层数组长度之间的关联。在SDS中,buf数组的长度不一定是字符数量加一,数组里面可以包含未使用的字节,而这些字节的数量就由SDS的free属性记录。通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略

空间预分配

空间预分配用于优化SDS的字符串增长操作,我们都知道当SDS的API对一个SDS进行修改时,除了分配给本身所需的字节空间,还会再额外分配一些备用空间。那么,这个备用空间是多大呢?备用空间由以下公式决定:

  • 如果对SDS进行修改后,SDS的长度(即len属性的值)小于1MB,那么程序分配和len属性同样大小的未使用空间,这时SDS的free属性的值将于len属性的值相同。比如经过修改之后,SDS的len将变为13个字节,那么程序也会分配13个字节的备用空间,外加一个字节用于存储空字符串标识字符串结束,所以SDS的buf数组实际长度为13+13+1=27字节
  • 如果对SDS进行修改之后,SDS的长度大于等于1MB,那么程序会多分配1MB的未使用时间。比如经修改后,SDS的len为30MB,那么程序会多分配1MB的未使用空间,SDS的buf数组的实际长度为30MB+10MB+1byte

惰性空间释放

惰性空间释放用于优化SDS的字符串缩短操作,当SDS的API需要缩短SDS保存的字符串时,程序其实并不立即使用内存重分配回收缩短后多出来的字节,而是将修改后尚未被使用的字节数存放在free中,用于以后使用

二进制安全

C字符串的必须必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符串将被误认到达字符串末尾,这限制了C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据

虽然数据库一般用于保存文本数据,但使用数据库来保存二进制数据的场景也不少见。因此,为了确保Redis可以适用于各种不同的场景,SDS的API都是二进制安全的,所有SDS API都会以处理二进制的方式来处理存放在buf数组里的数据,程序不会对其中的数据做任何限制、过滤、或假设,数据在写入时是什么样的,它被读取时就是什么样的

这也是我们将SDS的buf属性称为字节数组的原因,因为Redis不是用这个数组来保存字符,而是用它来保存一系列的二进制数据。且在SDS中,并非以一个空字符来判断是否到达字符数组的末尾,而是通过len属性的值,如图1-8

图1-8   保存了特殊数据格式的SDS

兼容部分C字符串函数

虽然SDS的API都是二进制安全的,但它们一样遵循C字符串以空字符结尾的惯例:这些API总会将SDS保存的数据的末尾设置为空字符,并且总会在为buf数组分配空间时多分配一个字节的空间来容纳空字符,这是为了让保存了文本数据的SDS可以重用一部分<string.h>库定义的函数。比如:strcasecmp是用于忽略大小写比较两个字符串的函数,使用它来对比SDS保存的字符串和另一个C字符串

strcasecmp(sds->buf, "hello world");

又或者,我们可以将sds中的buf所保存的内容,追加到另一个C字符串上。这样,就无需再去编写另外一套与<string.h>中功能类似的函数了

strcat(c_string, sds->buf);

现在,我们对C字符串和SDS的区别进行总结

 

C字符串

SDS

获取字符串长度的时间复杂度为O(N)

获取字符串长度的时间复杂度为O(1)

API是不安全的,可能会造成缓冲区溢出

API是安全的,不会造成缓冲区溢出

修改字符串长度N次必然需要执行N次内存重分配

修改字符串长度N次最多需要执行N次内存重分配

只能保存文本数据

可以保存文本或二进制数据

可以使用所有<string.h>库中的函数

可以使用一部分<string.h>库中的函数

表1-1   C字符串和SDS之间的区别

 

SDS API

函数

作用

时间复杂度

 sdsnew

创建一个包含给定C字符串的SDS 

O(N) ,N 为给定C字符串的长度 

sdsempty 

创建一个不包含任何内容的空SDS 

 O(1)

sdsfree 

释放给定的SDS 

O(1) 

sdslen 

返回SDS的已使用空间字节数 

这个值可以通过读取SDS的len属性来直接获得,复杂度为O(1) 

sdsavail 

返回SDS的未使用空间字节数 

这个值可以通过读取SDS的free属性来直接获得,复杂度为 O(1) 

sdsdup

创建一个给定SDS的副本(copy) 

O(N),N为给定SDS的长度 

sdsclear

清空SDS保存的字符串内容 

 因为惰性空间释放策略,复杂度为O(1)

sdscat 

 将给定C字符串拼接到SDS字符串的末尾

 O(N),N为被拼接C字符串的长度

sdscatsds

将给定SDS字符串拼接到另一个SDS字符串的末尾 

O(N),N为被拼接SDS字符串的长度 

sdscpy 

将给定的C字符串复制到SDS里面,覆盖SDS原有的字符串 

O(N),N为被复制C字符串的长度 

sdsgrowzero 

用空字符将SDS扩展至给定长度 

O(N),N为扩展新增的字节数 

sdsrange 

保留SDS给定区间内的数据,不在区间内的数据会被覆盖或清除 

O(N),N为被保留数据的字节数 

sdstrim 

接受一个SDS和一个C字符串作为参数,从SDS左右两端分别移除所有在C字符串中出现过的字符 

 O(M*N),M为SDS的长度,N为给定C字符串的长度

sdscmp 

对比两个SDS字符串是否相同 

O(N),N为两个SDS中较短的那个SDS的长度

表1-2   SDS的主要操作API  

参考链接:https://www.cnblogs.com/beiluowuzheng/p/9720121.html

以上是 Redis字符串 的全部内容, 来源链接: utcz.com/z/516394.html

回到顶部