Redis五大常用数据类型

在 Redis 官网,对于支持的数据类型,有这么一段描述:

它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。

常用命令

Redis 支持的命令比较多,对于常用的命令当然要记住,如果想不起来,可以去 http://www.redis.cn/commands.html 查阅。

基本操作指令

  • select 0 切换数据库
  • dbsize 查看当前库 key 的数量
  • flushdb 清空当前库
  • flushall 清空所有库

键 key 指令

  • keys * 查看当前库所有 key
  • exists key 检查某个 key 是否存在
  • type key 当前 key 的数据类型
  • del key 删除该 key 的数据
  • unlink key 非阻塞删除,仅将key 置为无效,后续异步删除
  • expire key times 为该 key 设置过期时间
  • ttl key 检查该key还有多久过期,-1为永不过期,-2为已过期
  • rename key newkey 将 key 重命名,如果newkey存在,相当于用 key 的 value 覆盖 newkey 的 value
keys 与 scan

虽然 keys * 可以查看所有的 key,但是当redis中key数量越大,keys 命令执行越慢,而且最重要的会阻塞服务器。因此一般建议使用 scan 命令代替 keys 命令获取所有 key。

Redis 字符串

字符串 string 是 Redis 最基本的类型,一个 string 最多存储 512M 数据。

String 类型是二进制安全的,你可以在 string 中放任何数据,比如一个序列化的对象,甚至一张图片。

string 常用命令

与 string 常用的命令主要是添加元素、获取元素、更新元素。

set

添加键值对。

set key value [EX seconds | PX milliseconds | KEEPTTL] [NX|XX]

  • EX key 失效时间秒数
  • PX key 失效时间毫秒数
  • NX 当 key 不存在时,可以将key-value 添加成功
  • XX 当 key 存在时,可以将 key-value 添加成功

get

查询对应的键值对。

append

将给定值追加到原值末尾。

strlen

字符串的长度。

setnx

当 key 不存在时,设置 key 的值。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

incr

将 key 中存储的数字增加 1,只能操作数据,如果为空,值会设置为 1.

decr

将 key 中存储的数字减少 1,只能操作数字,如果为空,值会被设置为 -1.

incrby / decrby

incr/decr 每次操作 1 个步长,incrby/decrby 可以指定步长。

自增原子性

Redis 的自增或者自减是原子操作,而Java中 i++/i– 并不是原子操作。Redis单命令的原子性主要得益于Redis的单线程。

mset/mget

相对于 set/get,同时处理多个 key 的存取。

setex

添加键值对的同时,设置过期时间,单位为秒。

getset

设置新值时获取旧值,以旧换新。

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> getset k1 s2
"v1"

Redis 字符串数据结构

Redis 为了提升性能,对于 string 的存储有很多处理。

  • 整数 字符串长度小于 21 且可以转换为整数
  • EmbeddedString 字符串长度小于限定值,不同redis版本限定值不同,一般在 40 左右
  • SDS 上两种不符合时,采用 sds

embstr 与 sds 区别也在于性能:

  • embstr 创建只分配一次内存,sds 为两次
  • embstr 可以更好地利用缓存优势(因为内容很小)
  • embstr 数据需要修改时,需要先转换为 sds

string的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配

SDS 底层实现

在C语言中,字符串可以用 \0 结尾的char数组标示。这种简单的字符串表示,在大多数情况下都能满足要求,但是不能高效地计算length和append数据。

SDS的数据结构如下,len表示sdshdr中数据的长度,free表示sdshdr中剩余的空间,buf表示实际存储数据的空间。

/*
 * 保存字符串对象的结构
 */
struct sdshdr {

    // buf 中已占用空间的长度
    int len;

    // buf 中剩余可用空间的长度
    int free;

    // 数据空间
    char buf[];
};

Redis 为了提升速度,在存储上有额外空间使用,因此 sds 比原始 c 语言的字符串更占用空间。

SDS 扩容

当字符串长度小于 1M 时,扩容都是2倍,如果超过 1M,每次扩容 1M。最大的长度为 512M。

释放内存时,会修改 len 和 free 字段,并不立即释放实际占用的内存空间。

Redis 列表

列表 list 是单键多值的结构。

Redis 列表是简单的字符串列表,按照插入顺序排序。可以添加元素到列表头部或者尾部。

列表常用指令

与 list 常用的命令主要是添加元素、获取元素、更新元素。

lpush

从左边添加一个或多个元素。

127.0.0.1:6379> lpush mylist v1 v2 v3 v4
(integer) 4

rpush

从右边添加一个或多个元素。

lpop

从左边取出一个元素。

127.0.0.1:6379> lpop mylist
"v4"
127.0.0.1:6379> lpop mylist
"v3"
127.0.0.1:6379> lpop mylist
"v2"
127.0.0.1:6379> lpop mylist
"v1"
127.0.0.1:6379> lpop mylist
(nil)

这条指令不仅仅是查询,取出元素后便不在 list 中,所有元素取出后,key 也随即失效。

rpop

从右边取出一个元素。

rpoplpush

从一个key 的右边取出一个元素,插入到另一个key左边。

lrang

按照索引下标获取多个元素(从左到右)

127.0.0.1:6379> lrange mylist 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1"

0 表示左边第一个,-1 表示右边第一个。

lindex

按照索引下标获得单个元素(从左到右)。

127.0.0.1:6379> lindex mylist 2
"v2"

llen

获得列表长度。

127.0.0.1:6379> llen mylist
(integer) 4

list 数据结构

List 的数据结构为快速链表 quickList。

在列表元素较少时,Redis 会采用一块连续的内存存储,结构为压缩链表 ziplist。

当数据量比较多时,采用 quicklist 。

这么做的原因是,普通链表需要的附加指针空间较大,会有一定空间浪费。

Redis 将链表和 ziplist 结合起来组成 quicklist。即将多个 ziplist 使用双向指针串联起来。这样既满足快速插入、删除元素的性能,又不会出现太大的空间浪费。

集合 Set

set 提供的功能与 list 类似,只不过它的数据是去重的。

此外,set 提供了一个指令可以用来判断某个元素是否在 set 中存在。

常用命令

与 set 常用的命令主要是添加元素、获取元素、删除元素。

sadd

将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略。

127.0.0.1:6379> sadd myset v1 v2 v3
(integer) 3

smembers

取出该集合的所有值。

127.0.0.1:6379> smembers myset
1) "v3"
2) "v2"
3) "v1"

sismember

判断集合中是否包含某个值,包含返回 1 ,不包含返回 0。

127.0.0.1:6379> sismember myset v3
(integer) 1
127.0.0.1:6379> sismember myset v5
(integer) 0

scard

返回该集合的元素个数。

127.0.0.1:6379> scard myset
(integer) 3

srem

删除集合中的某个元素。

127.0.0.1:6379> srem myset v3 v5
(integer) 1
127.0.0.1:6379> smembers myset
1) "v2"
2) "v1"

spop

随机从该集合中取出一个值。

sinter

返回两个集合的交集元素。

127.0.0.1:6379> sadd myset1 v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> sadd myset2 v4 v5 v6
(integer) 3
127.0.0.1:6379> sinter myset1 myset2
1) "v4"

sunion

返回两个集合的并集元素。

127.0.0.1:6379> sadd myset1 v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> sadd myset2 v4 v5 v6
(integer) 3
127.0.0.1:6379> sunion myset1 myset2
1) "v1"
2) "v2"
3) "v6"
4) "v5"
5) "v4"
6) "v3"

sdiff

返回两个集合的差集元素(key1中的,不包含key2中的)

127.0.0.1:6379> sadd myset1 v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> sadd myset2 v4 v5 v6
(integer) 3
127.0.0.1:6379> sdiff myset1 myset2
1) "v3"
2) "v1"
3) "v2"

set 数据结构

set 是 string 类型的无序集合,它底层是一个 value 为 null 的 hash 表,能做到添加/查找/删除元素复杂度 O(1)。

Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。

Hash 哈希

Redis hash 是一个键值对集合。

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

hash 类似 Java 里面的 Map<String,Object>

对象存储有多种方案,比较容易想到的为方案一,但是修改数据需要先对 value 反序列化,不是很方便。
方案二也是一种思路,但是这样 key 相对复杂一些,有数据冗余,并且数据比较分散。

Redis 采用的是方案三,整体是键值对,value 又是键值对。

redis hash

hash 常用指令

与 hash 相关的指令主要是添加与查看元素。

hset

<key> 集合中的 <field> 键赋值 <value>

hget

<key> 集合 <field> 取出 <value>

hmset

批量设置hash的值

hexists

查看哈希表 <key> 中,给定域 <field> 是否存在。

hkeys

列出该hash集合的所有<field>

hvals

列出该hash集合的所有<value>

hash 数据结构

Hash 类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。

当 field-value 长度较短且个数较少时,使用ziplist,否则使用hashtable。

有序集合 zset

Redis 的有序集合 zset(SortedSet)与 set 类似,是没有重复元素的字符串集合。

zset 给每个成员关联了一个评分 score,使得成员可以通过评分进行排序。

集合中的元素不能重复,但是评分可以重复。

常用命令

与 zset 常用的命令主要是添加元素、获取元素、删除元素。

zadd

将一个或多个 member 元素及其 score 值加入到有序集 key 当中。

127.0.0.1:6379> zadd myzset 1 v1 5 v2 3 v4
(integer) 3

zrange

返回有序集 key 中元素。

127.0.0.1:6379> zrange myzset 1 4
1) "v4"
2) "v2"
127.0.0.1:6379> zrange myzset 1 4 withscores
1) "v4"
2) "3"
3) "v2"
4) "5"

zcount

统计该集合,分数区间内的元素个数。

127.0.0.1:6379> zcount myzset 1 5
(integer) 3

zrank

返回该值在集合中的排名,从0开始,不存在的元素返回空 nil。

127.0.0.1:6379> zrank myzset v1
(integer) 0
127.0.0.1:6379> zrank myzset v4
(integer) 1
127.0.0.1:6379> zrank myzset v9
(nil)

zset 数据结构

zset底层使用了两个数据结构:

  • hash hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。
  • 跳跃表 跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。

zset 结构比较特殊,一方面它等价于Java的数据结构 Map<String, Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。

跳跃表

有序集合在实际应用中较为常见,例如排名、热榜等。

实现有序集合可以采用数组、平衡树、链表等,但是它们都有各自的优缺点。

  • 数组的插入、删除元素效率较低
  • 平衡树效率高但是结构复杂
  • 链表查询元素效率不高

Redis 在实现有序集合时,采用了跳跃表,它效率与平衡树相当,而实现复杂度低很多。

对于一个有序链表 1->3->4->5->7->8->9->NULL,从中查找元素8,需要从第一个元素开始依次查找、比较才能找到,共需要6次比较。

为了找到元素8,需要经过的元素 1->3->4->5->7->8

如果采用跳跃表。

先从第2层开始比较,找到4以后,第一层结束,开始第二层,找到7以后,其下一个元素已经大于8,开始找第0层,共需查找 4 次。

为了找到元素8,需要经过的元素 1->4->7->8

转载请注明出处:码谱记录 » Redis五大常用数据类型
标签: