Redis中数据类型的使用(hash和list)

(一)hash哈希

我们知道redis中的数据都是以键值对的方式存储的,key全部都是string类型,而value可以是不同的数据结构,其中就包括hash,也就是说,key这一层组织完成后到了value仍然是hash

1.Hash的一些命令

1)hset

设置hash中指定的字段(filed)和值value

我们首先要确定一个key,然后在这个key的value中,再添加一个hash结构

同时我们可以使用这个命令,同时添加多个key对应的哈希结构value

时间复杂度是O(1)如果插入多组就是O(n)

2)hget

获取hash中指定字段的值

我们需要给定一个key,然后再给定一个key中保存的filed

时间复杂度是O(1),如果我们查询的字段不存在就会返回nil

3)hexists

判断hash中是否有对应的field

返回值如果是1就表示存在,如果是0就表示不存在

时间复杂度也是O(1)

4) hdel

删除hash中指定的field,我们要注意,del是删除的key,而hdel是删除的field

我们可以通过这个命令,一次性删除多个field

时间复杂度:删除一个元素为O(1),n个元素为O(n)

5)hkeys

获取hash中的所有field

时间复杂度为O(n) ,N为field的个数

6)hvals

获取hash中的所有value

获取hash中的所有值

时间复杂度为O(n),N为field的个数

7)hgetall

获取hash中的field和value

时间复杂度O(n),N为field的个数

8)hmget

一次获取多个field中的value,我们刚刚的hget一次只能获取一个,而且之前说过,为了保证效率,我们要尽量减少网络的开销,所以我们可以使用hmget来一次获取多个value

这里其实还有hmset,可以一次性放入多个field和value,但是hset本身支持这个功能,所以这里我们不做说明

时间复杂度O(n),N为field的个数

9)hlen

获取hash中所有field的个数

时间复杂度O(1)

返回值为field的个数

10)hsetnx

在field不存在情况下设置hash中的field和value,如果存在就会失败,与setnx类似

时间复杂度为O(1),0表示失败,1表示成功

11)hincrby

用来使field对应的value进行+n的操作,我们都知道hash用来存键值对结构,所以当然也可以用来计数

但是我们使用这个命令时要注意,我们改变的值和增加的值,都需要是整数,不然就会报错

时间复杂度为O(1),返回值为变化后的值

12)hincrbyfloat

就是上一条指令的浮点数版本,用法是一样的,这里不多赘述

命令小结

我们上述也说了一些,一次查询多条数据的指令,但是我们在使用的时候要注意,我们redis可能做缓存也可能做服务器,所以如果我们查询的数据很多,就会导致阻塞,可能会导致出现服务器一瞬间压力过大引发一系列其他问题,而且我们上述的h系列的命令,必须要保证key对应的value必须是hash类型的

那如果我们一定要获取到所有的field数据,我们可以使用hscan,这个遍历redis的hash的渐进式遍历的,也就是一次遍历一小部分,分多次遍历所有的field,这样就不会阻塞住redis了

2.hash的内部编码

hash的内部编码有两种

ziplist(压缩列表):当hash类型的元素个数小于一定值时,redis就会使用ziplist来作为hash的内部实现,使用ziplist可以更加进奏的实现多个元素的连续存储,所以可以很好的节省空间,但是如果我们hash类型元素过多,就会导致读写效率会变得很慢

hashtable(哈希表):当元素个数比较多时,redis就会使用hashtable来作为内部实现,因为我们说元素个数多,就会导致ziplist的读写效率下降,但是hashtable的读写时间复杂度为O(1)

也就是说,ziplist是用时间换空间,但是hashtable是用空间换时间,而我们之前也说过,空间的话我们现在的硬件还是比较够用的,但是时间还是需要我们尽可能的去节省

3.hash的一些应用场景

1)作为缓存

我们之前使用的mysql是关系型数据库,用户的属性和信息表现为一个表

但是我们redis是使用一个个键值对,所以就会通过映射的方式来表示用户的信息,我们可以使用字符串的json格式

但是这样仍然不够直观,我们就可以通过今天的hash类型来存储

缓存方式对比:

1).如果我们使用原生的string类型

虽然也可以表示用户信息实现也很简单,但是会导致内存的占用量比较大,而且用户信息比较分散,因为每个key都是不同的,不满足高内聚的特点

2).如果我们序列化字符串使用json格式

优点:针对总是以整体作为操作的信息⽐较合适,编程也简单。同时,如果序列化⽅案选择合适,内存的使⽤效率很⾼

缺点:我们序列化和反序列化也有一定的开销,如果总操作个别属性就不是很灵活

3).hash类型

优点:简单,灵活,直观,同时可以很方便的存储和获取信息

缺点:在内部涉及到内部编码ziplist和hashtable的转换,可能会对内存造成消耗

(二)list列表

list就相当于顺序表,支持头插头删,尾插尾删,所以list内部的编码方式并非是一个简单的数据,更像是双端队列

同时,列表的元素是有序的,这里的有序是指顺序不同会导致结果不同,并不是升序和降序的有序,而且列表中的元素的允许重复的

1.list的一些命令

1)lpush

用来头插到list中(左头右尾),支持同时插入多个元素

时间复杂度O(1),如果插入多个元素就为O(n)n为插入元素的个数

返回值是插入后list的长度

2)lpushx

当key存在时就将元素头插到list中,不存在就直接返回

时间复杂度:只插⼊⼀个元素为O(1),插⼊多个元素为O(N),N为插⼊元素个数.

返回值:插⼊后list的⻓度。

3)rpush

把一个或多个元素尾插到list中

时间复杂度为O(1),插入N个为O(N)

返回值为list插入后的长度

4)rpushx

当key存在时就把一个或多个元素尾插到list中

时间复杂度:只插⼊⼀个元素为O(1),插⼊多个元素为O(N),N为插⼊元素个数.

返回值:插⼊后list的⻓度

5)lrange

我们这里的l是代表list并不是left

获取从start开始到end区间的所有元素,左右都是闭区间,并且因为redis支持负数下标,如果我们要获取到最后一个元素可以使用-1,并且redis下标与数组一样,都是从0开始

同时这里redis有一个很好的点

我们在c++中,如果下标超出了范围,我们一般会认为这是一个"未定义的行为"可能会导致程序崩溃,也可能会出现不合法或者合法的数据,这就会导致我们不一定会立刻发现问题,但是这种不负责的行为,效率确实是比较高

在java中,如果我们下标超出范围,会给我们抛出异常,但是因为要给我们多做一步下标合法性的验证,就会导致速度会比较慢,但是我们可以第一时间发现问题

而redis是尽可能的去获取到给定区间的元素,如果我们给了一个非法区间,比如超出下标,那么redis也会返回可以获取到下标元素的值(拥有鲁棒性,容错能力强)

6)lpop

头删,从list左侧取出元素

时间复杂度:O(1)

返回值:取出的元素或者nil。

7)rpop

尾删,从list右侧取出元素

时间复杂度:O(1)

返回值:取出的元素或者nil。

我们这里要注意,在当前的redis5中并没有count参数,但是redis6.2以后,新增了count参数,描述这一次要删几个元素

同时搭配这几个出队入队操作,可以实现,栈,队列,双端队列等数据结构

8)lindex

获取从左数第index位置的元素

时间复杂度:O(N)

返回值:取出的元素或者nil。

9)linsert

在特定的位置插入元素

我们发现我们在111签名插入1010,但是有两个111,所以我们会在最前面的一个111前面加

时间复杂度为O(N),返回值为插入后的list长度

10)llen

获取到list的长度

时间复杂度O(1),返回值为list的长度

11)lrem

删除某个值的一些元素

12)ltrim

保留start和stop之间的元素,两边外的元素直接被删除

13)lset

根据下标修改元素

我们如果想在不存在的下标上设置元素会直接返回nil

如果我们想删除一个指定位置的元素,我们可以通过lset把这个位置的元素设置为一特定的字符,然后通过lrem来进行删除

2.阻塞版本的命令

blpop和brpop是lpop和rpop的阻塞版本,他们的区别为:

1).如果列表有元素,那么阻塞和非阻塞是一样的,如果没有元素,非阻塞版本会返回nil,阻塞版本会根据阻塞时间,进行阻塞,这是redis可以执行一些别的命令,但是要求执行命令的客户端为阻塞状态。

2).命令中如果设置了多个间,就会从左向右遍历,一旦有一个键对应的列表有元素,就会弹出并且返回

3).如果有多个客户端执行blpop,最先执行命令的客户端会得到弹出的元素

我们redis中的list就相当于一个阻塞队列,我们之前在多线程上也说到过阻塞队列,但是当时我们需要手动保证线程安全,但是redis的线程安全是通过线程安全的情况,而因为我们redis的空间还是比较大的,一般不考虑队列满的情况

blpop

lpop的阻塞版本

返回值为列表和取出的元素或者nil

时间复杂度O(1)

brpop

rpop的阻塞版本

时间复杂度O(1)

返回值为取出的元素或nil

3.lsit的内部编码

1.ziplist(压缩列表)

我们之前在hash也说过,他的内部编码也是ziplist,ziplist把数据按照更紧凑的压缩形式进行标识,能够很好的节省空间,但是如果数据比较多操作数据的效率就会比较低

2.linkedlist(链表)

ziplist如果数据多,操作数据的效率会比较低,所以我们可以通过linkedlist来操作数据,虽然这样会使我们占用的空间变大,但是对数据的操作会比较快

但是这两种方式各有各的好处,有没有一种编码方式可以综合这两种的特点?

我们redis在之后使用了quicklist来内部编码list类型,quicklist相当于使list和ziplist的结合,整体还是一个链表,但是链表的每一个节点,是一个ziplist,我们通过链表来保证每一个ziplist都不是很大,这样就可以在节省空间的前提下,尽可能的快速的操作数据

4.使用场景

消息队列

我们说list给我们提供了阻塞状态的lpop和rpop,所以我们可以用这个阻塞实现生产者消费者模型,我们生产者客户端用lpush从列表左边插入元素,然后消费者用brpop来阻塞式的去拿队首元素

分频道的消息队列

redis使用lpush和brpop命令,但是通过不同键模拟不同频道的概念,不同的消费者可以通过brpop不同的键

作为数组

我们可以把list作为类似数组的一个结构,来存储多个元素,具体要如何组织数据,需要根据实际业务来确定

相关推荐
BangRaJun1 小时前
LNCollectionView-替换幂率流体
算法·ios·设计
2401_871151071 小时前
12月第十九讲:Redis应用Redis相关解决方案
数据库·redis·缓存
苓诣1 小时前
寻找重复数
数据结构·算法·leetcode
重生之Java开发工程师2 小时前
ArrayList与LinkedList、Vector的区别
java·数据结构·算法·面试
Wils0nEdwards2 小时前
Leetcode 串联所有单词的子串
java·算法·leetcode
埃菲尔铁塔_CV算法2 小时前
新型滤波算法在信号处理中的创新与应用
算法·信号处理
toto4123 小时前
Java中的锁机制 与 synchronized的理解
java·算法
dundunmm4 小时前
机器学习之KNN算法
人工智能·算法·机器学习·数据挖掘·knn·分类算法
蓝胖子教编程4 小时前
【题解】【枚举】——[NOIP2018 普及组] 龙虎斗
c++·算法
奇偶变不变4 小时前
RTOS之事件集
java·linux·jvm·单片机·算法