Redis5种数据类型

/ Redis / 没有评论 / 481浏览

今天开始我分享一下Redis中的5种数据结构。下面我们看第一种数据结构字符串。字符串是Redis中的最基础的数据结构。我们保存到Redis中的key,也就是键,就是字符串结构的,除此之外,我们以后学习的其它数据结构,也是在字符串的基础上设计的,可见字符串结构对于Redis是多么的重要。字符串中的值虽然是字符串但是可以保存很多种类型的如:简单的字符串、JSON、XML、二进制等等。但有一点要特别注意,就是在Redis中字符串类型的值最大只能保存512MB。


img


  1. 设置值
set key value [EX seconds] [PX milliseconds] [NX|XX]

img

set命令有几个非必须的选项,下面我们看一下它们具体的说明

下面我们看一下setnx和setxx命令在实际的开发中,有什么作用呢?我们知道setnx命令只有当然key不存在的时候才能设置成功,换句话说,也就是同一个key在执行setnx命令时,只能成功一次,并且由于Redis的单线程命令处理机制,即使多个客户端同时执行setnx命令,也只人有一个客户端执行成功。所以,正是基于setnx命令的这种特性,所以setnx命令可以作为分布式锁的一种解决方案。


2.获取值

get key

img


3.批量设置值

mset key value

img


4.批量获取值

mget key

img

如果有些键不存在,那么它的值将为nil也就是空,并且返回的结果,就是按照传入键的顺序返回的。

img


5.计数

incr key

incr命令用于对值做自增操作,返回的结果分为3种情况:

除此之外,在Redis中除了有incr自增命令外,还提供了很多其它的有关对数字处理的命令。例如:

decr key 自减
incrby kek increment 自增指定数字
decrby key decrement 自减指定数字
incrbyfloat key increment 自增浮点数

img


6.追加值

append key value

append命令可以向字符串尾部追加值。 img


7.字符串长度

strlen key

img

由于每个中文占用3个字节,所以jilinwula这个键,返回是字符串长度为12,而不是4。


8.设置并返回原值

getset key value

img


9.设置指定位置的字符

setrange key offeset value

img


10.获取部分字符串

getrange key start end

img

命令时间复杂度
set key valueO(1)
get keyO(1)
del keyO(k) k是键的个数
mset key valueO(k) k是键的个数
mget keyO(k) k是键的个数
incr keyO(1)
decr keyO(1)
incrby key incrementO(1)
decrby keky incrementO(1)
incrbyfloat key iincrementO(1)
append key valueO(1)
strlen keyO(1)
setrange key offset valueO(1)
getrange key start endO(n) n是字符串长度

img


下面我们介绍第二种数据类型,哈希类型。大部分语言基本都提供了哈希类型,如Java语言中的Map类型及Python语言中的字典类型等等。虽然语言不同,但它们基本使用都是一样的。也就是都是键值对结构的。例如:

value={{field1, value1}

下面我们通过下图来直观感受一下字符串类型和哈希类型的区别。

img

Redis中哈希类型都是键值对结构的,所以要特别注意这里的value并不是指的是Redis中的key的value,而是Redis中哈希类型中的field所对应的value。

下面我们还是和介绍字符串类型一样,先是了解一下Redis中哈希类型的相关命令。

命令


一. 设置值

hset key field value 

img

我们看上图执行的命令知道,hset命令也是有返回值的。如果hset命令设置成功,则返回1,否则则返回0。除此之外Redis也为哈希类型提供了hsetnx命令。在字符串那篇文章中,我们知道,nx命令则表示key不存在的时候,才能设置成功,而在Redis中hsetnx命令则表示field不存在的时候,才能设置成功。


二. 获取值

hget key field

img 我们看hget命令和get有很大的不同,get命令在获取的时候,只要写一个名字就可以了,而hget命令则要写两个名字,第一个名字就是key 第二个名字就是field。除此之外,当然key或者field不存在时,返回的结果都是nil。


三. 删除field

hdel key field [field ...]

img

hdel命令删除的时候,也会有返回值,并且这个返回就是成功删除field的个数。当field不存在时,并不会报错,而是直接返回0。


四. 计算field个数

hlen key

img

hlen命令返回的就是当前key中field的个数,如果key不存在,则返回0。


五. 批量设置或获取field-value

hmget key field [field ...]
hmset key field value [field value ...]

img

hmset命令和hmget命令分别都是批量设置和获取值的,hmset命令没有什么要注意的,但hmget命令要特别注意,当我们获取一个不存在的key或者不存在的field时,Redis并不会报错,而是返回nil。并且有几个field不存在,则Redis返回几个nil,当整个key都不存在时,则返回全部nil(有几个field返回几个nil)。


六. 判断field是否存在

hexists key field

img

当执行hexists命令时,如果当前key包括field,则返回1,否则返回0。

七. 获取所有field

hkeys key

img


八. 获取所有value

hvals key

img


九. 获取所有的field-value

hgetall key

img

hgetall命令会返回当前key中的所有field-value,并按照顺序依次返回,也就是field-value field-value等等。


十. hincrby hincrbyfloat

hincrby key field increment
hincrbyfloat key field increment

img

hincrby命令和incrby命令的使用功能基本一样,都是对值进行增量操作的。唯一不同的就是incrby命令的作用域key,而hincrby命令的作用域则是field。


十一. 计算value的字符串长度

hstrlen key field

img

hstrlen命令返回的是当前key中field中字符串的长度,如果当前key中没有field则返回0。


哈希类型命令的时间复杂度

命令时间复杂度
hset key field valueO(1)
hget key fieldO(1)
hdel key field [field ...]O(k) ,k是field个数
hlen keyO(1)
hgetall keyO(n) ,n是field总数
hmget key field [field ...]O(k) ,k是field个数
hmset key field value [field value ...]O(k) ,k是field个数
hexists key fieldO(1)
hkeys keyO(n) ,n是field总数
hvals keyO(n) ,n是field总数
hsetnx key field valueO(1)
hincrby key field incrementO(1)
hincrbyfloat key field incrementO(1)
hstrlen key fieldO(1)

内部编码

在Redis哈希类型的内部编码只有两种它们分别是:

下面我们通过以下命令来演示一下ziplist和hashtable这两种内部编码。

  1. 当field个数比较少并且value也不是很大时候Redis哈希类型的内部编码为ziplist。 img

  2. 当value中的字节数大于64字节时(可以通过hash-max-ziplist-value设置),内部编码会由ziplist变成hashtable。 img 因为在Redis中中文的字节数比英文的字节数大大,正常来说,一个中文占用3个字节。

  3. 当field个数超过512(可以通过hash-max-ziplist-entries参数设置),内部编码也会由ziplist变成hashtable。 由于直接创建512个field不方便,为了更好的验证该功能,我将用程序的方式,动态创建512个field来验证此功能,下面为具体的代码。

import redis

r = redis.Redis(host='127.0.0.1', port=6379)

print('Key为【userinfo】的字节编码为【%s】' % r.object('encoding', 'userinfo').decode('utf-8'))

for i in range(1,513):
    r.hset('userinfo', i, '吉林乌拉')

print('Key为【userinfo】的字节编码为【%s】' % r.object('encoding', 'userinfo').decode('utf-8'))
Key为【userinfo】的字节编码为【ziplist】
Key为【userinfo】的字节编码为【hashtable】

Redis中的列表类型。在Redis中列表类型,可以简单的理解为存储多个有序字符串的一种新类型,这种类型除了字符串类型中已有的功能外,还提供了其它的功能。如可以对列表的两端插入和弹出元素(在列表中的字符串都可以称之为元素),除此之外还可以获取指定的元素列表,并且还可以通过索引下标获取指定元素等等。下面我们通过下图来看一下Redis中列表类型的插入和弹出操作:

img

下面我们看一下Redis中列表类型的获取与删除操作:

img

下面我们看一下Redis列表类型的特点:

lindex命令

并且在Redis中列表类型的索引是从0开始的。

img

下面我们还是和学习其它数据类型一样,我们还是先学习一下Redis列表类型的命令。

命令

一、添加操作

1.从右边插入元素

rpush key value [value ...]

img

我们看rpush命令在插入时,是有返回值的,返回值的数量就是当前列表中所有元素的个数。

我们也可以用下面的命令从左到右获取当前列表中的所有的元素,也就是如上图所示中那样。

lrange 0 -1 

2.从左边插入元素

lpush key value [value ...]

img

lpush命令的返回值及用法和rpush命令一样,这里就不在介绍了,但通过上面的事例证明了我们前面说的,因为当前key中已经有了3个元素了,所以我们在执行插入命令时,返回的就是6而不是3,因为rpush命令和lpush命令的返回值并不是当前插入元素的个数,而返回的是当前key中全部元素的个数。


3.向某个元素前或者后插入元素

linsert key BEFORE|AFTER pivot value

img

linsert命令在执行的时候首先会从当前列表中查找到pivot元素,其次在将这个新元素插入到pivot元素的前面或者后面。并且我们通过上图所示知道linsert命令在执行成功后也是会有返回值的,返回的结果就是当前列表中元素的个数。


二、查找

1.获取指定范围内的元素列表

lrange key start stop

img

lrange命令会获取列表中指定索引范围的所有元素。通过索引获取列表主要有两个特点:


2.获取列表中指定索引下标的元素

lindex key index

img


3.获取列表长度

llen key

img


三、删除

1.从列表左侧弹出元素

lpop key

img

lpop命令执行成功后会返回当前被删除的元素名称。


2.从列表右侧弹出元素

rpop key

img

rpop命令和lpop命令的使用方式一样,这里不在做过多介绍了。


3.删除指定元素

lrem key count value

lrem命令会将列表中等于value的元素删除掉,并且会根据count参数,来决定删除value的元素个数。下面我们看一下count参数的使用说明:


4.按照索引范围修剪列表

ltrim key start stop

ltrim命令会直接保留start索引到stop索引的之间的元素,并包括当前元素,而之外的元素则都会删除掉,所以该命令也叫修剪列表。

img

并且有一点要注意ltrim命令不会返回当前的列表中元素的个数,而是返回改命令是否成功的状态。


四、修改

1.修改指定索引下标的元素

lset key index value

img


五、阻塞操作

blpop key [key ...] timeout
brpop key [key ...] timeout

blpop和brpop命令是lpop和rpop命令的阻塞版本,它们除了弹出方向不同以外,使用方法基本相同。

下面我们看一下该命令的详细使用。

  1. 列表为空:如果timeout=3,则表示客户端等待3秒后才能返回结果,如果timeout=0,则表示客户端会一直等待,也就是会阻塞。 img 由于我期间向列表中插入了元素,否则上述命令将一直阻塞下去。 2.列表不为空:如果timeout=0,并且列表不为空时,则blpop和brpop命令会立即返回结果,不会阻塞。 img

下面我们看一下blpop和brpop命令的注意事项。


下面我们看一下列表中命令的相关时间复杂度。

操作类型命令时间复杂度
添加rpush key value [value ...]O(k),k是元素的个数
添加lpush key value [value ...]O(k),k是元素的个数
添加linsert key BEFORE/AFTER pivot valueO(n),n是pivot距离列表头或者尾的距离
添加lrange key start stopO(s + n),s是start偏移量,n是start到stop的范围
添加lindex key indexO(n),n是索引的偏移量
添加llen keyO(1)
添加lpop keyO(1)
添加rpop keyO(1)
添加lrem key count valueO(n),n是列表长度
添加ltrim key start stopO(n),n是要裁剪的元素个数
添加lset key index valueO(n),n是索引的偏移量
添加blpop/brpop key [key ...] timeoutO(1)

内部编码

列表中的内部编码有两种,它们分别是:

1.当元素个数较少并且没有大元素时,内部编码为ziplist。 img 我们看当我们在列表中插入少量元素,并且没有大元素时,返回的内部编码并不是ziplist而是quicklist。这又是什么编码呢。简单来说,quicklist编码是Redis3.2版本之后(包括当前版本)提供了一种新的内部编码。它和ziplist和linkedlist编码相比它结合了这两种编码的优势,具体的区别以后的文章中在做详细介绍。由于我电脑安装的Redis的版本是4.0.9,在3.2版本之后所以,上述代码执行后的内部编码为quicklist。


2.当元素个数超过512个元素时,内部编码将变为linkedlist。以下代码都是本人在Redis4.0.9中的测试,所以上述的验证本人没有验证成功。

import redis

r = redis.Redis(host='127.0.0.1', port=6379)

print('Key为【listkey】的字节编码为【%s】' % r.object('encoding', 'listkey').decode('utf-8'))

for i in range(1,512):
    r.rpush('listkey', i)

print('Key为【listkey的字节编码为【%s】' % r.object('encoding', 'listkey').decode('utf-8'))
Key为【listkey】的字节编码为【quicklist】
Key为【listkey的字节编码为【quicklist】

3.当列表中某个元素超过64个字节时,内部编码也会变成linkedlist。以下代码也是本人在Redis4.0.9中的测试,所以上述的验证本人也没有验证成功。

import redis

r = redis.Redis(host='127.0.0.1', port=6379)

print('Key为【listkey】的字节编码为【%s】' % r.object('encoding', 'listkey').decode('utf-8'))

value = ''

for i in range(1,512):
    value += str(i)

r.rpush('listkey', i)

print('Key为【listkey的字节编码为【%s】' % r.object('encoding', 'listkey').decode('utf-8'))
Key为【listkey】的字节编码为【quicklist】
Key为【listkey的字节编码为【quicklist】

Redis中的集合类型,也就是set集合。在Redis中set也是可以保存多个字符串的。那么set集合和list链表到底有什么不同呢?下面我们重点介绍一下它们之间的不同。

下面我们介绍一下set中的相关命令。


命令

一、集合内操作

1.添加元素

sadd key member [member ...]

img

sadd命令也是有返回值的,它的返回值就是当前执行sadd命令成功添加元素的个数,因为set中不能保存重复元素,所以在执行:

sadd setkey c d

命令时,返回的是1,而不是2。因为元素c,已经成功保存到set中,不在继续保存了,只能将d保存到set中。


2.删除元素

srem key member [member ...]

img

srem命令和sadd命令一样也是有返回值的,返回值就是当前删除元素的个数。


3.计算元素个数

scard key

img

scard命令的时间复杂度为O(1),scard命令不会遍历set中的所有元素,而是直接使用Redis中的内部变量。


4.判读元素是否在集合中

sismember key member

img

sismember命令也有返回值,如果返回值为1则表示当前元素在当前set中,如果返回0则表示当前元素不在set中。


5.随机从set中返回指定个数元素

srandmember key [count]

img

srandmember命令中有一个可选参数count,count参数指的是返回元素的个数,如果当前set中的元素个数小于count,则srandmember命令返回当前set中的所有元素,如果count参数等于0,则不返回任何数据,如果count参数小于0,则随机返回当前count个数的元素,不管当前set中的元素个数为多少。


6.从集合中随机弹出元素

spop key [count]

img

spop命令也是随机从set中弹出元素,并且也支持count可选参数,但有一点和srandmember命令不同。spop命令在随机弹出元素之后,会将弹出的元素从set中删除,而srandmember命令则不同,只会随机弹出元素,并不会将元素从set中删除。


7.获取所有元素

smembers key

img

smembers命令虽然能获取当前set中所有的元素,但smembers命令返回元素的顺序与sadd添加元素的顺序不一定相同,这也就是前面提到过的保存在set中的元素是无序的。


二、集合间操作

1.集合的交集

sinter key [key ...]

img


2.集合的并集

sunion key [key ...]

img


3.集合的差集

sdiff key [key ...]

img


4.将集合的交集、并集、差集的结果保存

sinterstore destination key [key ...]
sunionstore destination key [key ...]
sdiffstore destination key [key ...]

img

为什么Redis要提供了sinterstore、sunionstore、sdiffstore命令来将集合的交集、并集、差集的结果保存起来呢?这是因为Redis在进行上述比较时,会比较耗费时间,所以为了提高性能可以将交集、并集、差集的结果提前保存时来,这样在需要使用时,可以直接通过smembers命令获取。


下面我们看一下set中相关命令的时间复杂度。

命令时间复杂度
sadd key member [member ...]O(k),k是元素的个数
srem key member [member ...]O(k),k是元素的个数
scard keyO(1)
sismember key memberO(1)
srandmember key [count]O(count)
spop key [count]O(1)
smembers keyO(n),n是元素的总数
sinter key [key ...]O(m * k),k是多个集合中元素最少的个数,m是键个数
sunion key [key ...]O(k),k是多个元素个数和
sdiff key [key ...]O(k),k是多个元素个数和
sinterstore destination key [key ...]O(m * k),k是多个集合中元素最少的个数,m是键个数
sunionstore destination key [key ...]O(k),k是多个元素个数和
sdiffstore destination key [key ...]O(k),k是多个元素个数和

内部编码

备注:我们可以通过set-max-intset-entries参数来设置上述中的默认参数。


下面我们看一下具体的事例,来验证我们上面提到的内部编码。

1.当元素个数较少并且都是整数时,内部编码为intset。 img


2.当元素不全是整数时,内部编码为hashtable。 img


3.当元素个数超过512个时,内部编码为hashtable。

import redis

r = redis.Redis(host='127.0.0.1', port=6379)

if r.object('encoding', 'setkey') != None:
    print('Key为【setkey】的字节编码为【%s】' % r.object('encoding', 'setkey').decode('utf-8'))

for i in range(1, 600):
    r.sadd('setkey', i)

if r.object('encoding', 'setkey') != None:
    print('Key为【setkey】的字节编码为【%s】' % r.object('encoding', 'setkey').decode('utf-8'))

Key为【setkey】的字节编码为【intset】
Key为【setkey】的字节编码为【hashtable】

有序集合类型,也是Redis中的5大数据类型中的最后一个。看名字,我们就知道,有序集合也是一种集合,并且这个集合还是有序的。那有序集合和列表有什么不同呢?因为列表也是有序的。 它们到底有什么不同呢?有序集合的有序和列表的有序是不同的。列表中的有序指的的是插入元素的顺序,和查询元素的顺序相同。而有序集合中的有序指的是它会为每个元素设置一个分数(score),而查询时可以通过分数计算元素的排名,然后在返回结果。因为有序集合也是集合类型,所以有序集合中也是不插入重复元素的,但在有序集合中分数则是可以重复,那如果在有序集合中有多个元素的分数是相同的,那么这些重复的元素的排名是怎么计算的呢?在下面的内容中我们在做详细说明。下面我们看一下列表、集合、有序集合的它们3个数据类型之间的区别。

数据结构是否允许重复元素是否有序有序实现方式应用场景
列表索引下标时间轴、消息队列
集合标签、社交
有序集合分数排行榜、社交

下面我们重点了解一下Redis中有序集合的相关命令。

命令

一、集合内

1.添加元素

zadd key [NX|XX] [CH] [INCR] score member [score member ...]

img

zadd命令也是有返回值的,返回值就是当前zadd命令成功添加元素的个数。除此之外,zadd命令还有很多其它的选填参数。下面我们详细了解一下:

备注:由于有序集合相比集合提供了排序字段,正是因为如此也付出了相应的代价,,zadd的时间复杂度为O(log(n)),sadd的时间复杂度为O(1)。


2.计算成员个数

zcard key

img


3.计算某个成员的分数

zscore key member

img

在使用zscore命令时,如果key不存在,或者元素不存在时,该命令返回的都是(nil)。


4.计算成员的排名

zrank key member
zrevrank key member

img

zrank命令是从分数低到高排名,而zrevrank命令则恰恰相反,从高到低排名。有一点要特别注意zrank和zrevrank命令与zscore命令不同的前者是通过分数计算出最后的排名,而后者则是直接返回当前元素的分数。


5.删除元素

zrem key member [member ...]

img

返回的结果为成功删除元素的个数,因为zrem命令是支持批量删除的。


6.增加元素分数

zincrby key increment member

img

虽然zincrby命令是增加元素分数的,但我们也可以指定负数,这样当前元素的分数,则会相减。


7.返回指定排名范围的元素

zrange key start stop [WITHSCORES]
zrevrange key start stop [WITHSCORES]

img

zrange命令是通过分数从低到高返回数据,而zrevrange命令是通过分数从高到低返回数据。如果执行命令时如果添加了WITHSCORES可选参数,而返回数据时会返回当前元素的分数。


8.返回指定分数范围的元素

zrangebyscore key min max [WITHSCORES] [LIMIT offset count]
zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count]

img

min和max参数同时还支持开区间(小括号)和闭区间(中括号),同时我们还可以用-inf和+inf参数代表无限小和无限大。

img

备注:闭区间的没有验证成功,执行时会报错,这个有待验证。


9.返回指定分数范围元素个数

zcount key min max

img


10.删除指定指定排名内的升序元素

zremrangebyrank key start stop

img


11.删除指定分数范围元素

zremrangebyscore key min max

img


二、集合间操作

1.交集

zinterstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]

因为zinterstore命令参数比较多,所以下面我们详细了解一下:


2.并集

zunionstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]

img

zunionstore命令的相关参数和zinterstore命令相同。


时间复杂度

下面我们了解一下上述命令的时间复杂度。

命令时间复杂度
zadd key [NX/XX] [CH] [INCR] score member [score member ...]O(k*log(n)),k是添加元素的个数,n是当前有序集合元素个数
zcard keyO(1)
zscore key memberO(1)
zrank key memberO(log(n)),n是当前有序集合元素个数
zrevrank key memberO(log(n)),n是当前有序集合元素个数
zrem key member [member ...]O(k*log(n)),k是删除元素的个数,n是当前有序集合元素个数
zincrby key increment memberO(log(n)),n是当前有序集合元素个数
zrange key start stop [WITHSCORES]O(log(n) + k),k是要获取的元素个数,n是当前有序集合个数
zrevrange key start stop [WITHSCORES]O(log(n) + k),k是要获取的元素个数,n是当前有序集合个数
zrangebyscore key min max [WITHSCORES] [LIMIT offset count]O(log(n) + k),k是要获取的元素个数,n是当前有序集合个数
zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count]O(log(n) + k),k是要获取的元素个数,n是当前有序集合个数
zcount key min maxO(log(n)),n是当前有序集合元素个数
zremrangebyrank key start stopO(log(n) + k),k是要删除的元素个数,n是当前有序集合个数
zremrangebyscore key min maxO(log(n) + k),k是要删除的元素个数,n是当前有序集合个数
zinterstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM/MIN/MAX]O(n * k) + O(m * log(m)),n是元素元素最小的有序集合元素个数,k是有序集合个数,m是结果集中元素个数
zunionstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM/MIN/MAX]O(n) + O(m * log(m)),n是所有有序集合元素个数和,m是结果集中元素个数。

内部编码

有序集合类型的内部编码有两种,它们分别是:

备注:上述中的默认值,也可以通过以下参数设置:zset-max-ziplist-entries和zset-max-ziplist-value。


下面我们用以下示例来验证上述结论。

1.当元素个数比较少,并且每个元素也比较小时,内部编码为ziplist:

img


2.当元素个数超过128时,内部编码为skiplist。下面我们将采用python动态创建128个元素,下面为源码:

import redis

r = redis.Redis(host='127.0.0.1', port=6379)

if r.object('encoding', 'zsetkey') != None:
    print('Key为【zsetkey】的字节编码为【%s】' % r.object('encoding', 'zsetkey').decode('utf-8'))

for i in range(1, 600):
    r.zadd('zsetkey',i,1)

if r.object('encoding', 'zsetkey') != None:
    print('Key为【zsetkey】的字节编码为【%s】' % r.object('encoding', 'zsetkey').decode('utf-8'))
Key为【zsetkey】的字节编码为【ziplist】
Key为【zsetkey】的字节编码为【skiplist】

3.当有序集合中有任何一个元素大于64个字节时,内部编码为skiplist。

import redis

r = redis.Redis(host='127.0.0.1', port=6379)

if r.object('encoding', 'zsetkey') != None:
    print('Key为【zsetkey】的字节编码为【%s】' % r.object('encoding', 'zsetkey').decode('utf-8'))

value = ''
for i in range(1, 600):
    value += str(i)

r.zadd('zsetkey',value,1)

if r.object('encoding', 'zsetkey') != None:
    print('Key为【zsetkey】的字节编码为【%s】' % r.object('encoding', 'zsetkey').decode('utf-8'))
Key为【zsetkey】的字节编码为【skiplist】

img


上述内容就是Redis中5种数据类型的内容,如有不正确的地方,欢迎留言,谢谢。