【Redis教程0x03】详解Redis的基本数据类型

引言

根据【Redis教程0x02】中介绍的,Redis的数据类型可分为5种基本数据类型(String、Hash、List、Set、Zset)和4种高级数据类型(BitMap、HyperLogLog、GEO、Stream)。在本篇博客中,我们将详解这9种数据类型,分别从介绍、内部实现、常用指令、应用场景四个维度来说明。此外,还要埋个伏笔,Redis的9种数据类型的底层实现主要依赖了8种数据结构(SDS、LinkedList、Dict、SkipList、Intset、ZipList、QuickList),我们后面也会讨论。废话不多说,让我们从5种基本数据类型开始学习吧。
这里有个在线Redis环境,网页上就能使用:https://try.redis.io/

基本数据类型

字符串String

介绍

字符串String是Redis中最基本也是我们最常用的数据类型,它是基于key-value结构,key是唯一标识,value是具体的值。String是一种二进制安全的数据类型,其value可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的base64编码或者解码或者url)、序列化后的对象。value最多可以容纳的数据长度是512MB

底层实现

Redis的String类型的底层数据结构实现主要是int和SDS(Simple Dynamic String,简单动态字符串)。虽然Redis是用C语言写的,但是SDS跟C语言的原生字符串并不相同,具体表现如下:

  • SDS不仅可以保存文本数据,还可以保存二进制数据。因为SDS使用len属性的值而不是空字符串来判断是否结束,并且SDS的所有API都会处理二进制的方式来处理SDS存放在buf[]数组里的数据。这也是为什么SDS能存放文本、图片、音频、视频、压缩文件这样的二进制数据的原因。
  • SDS获取字符串长度的时间复杂度为O(1)。C语言不记录字符串长度,所以获取复杂度为O(n)。而SDS结构里用len属性记录。
  • Redis的SDS API是安全的,拼接字符串不会造成缓冲区溢出。因为SDS在拼接字符串之前会检查SDS空间是否满足要求,空间不够会自动扩容,所以不会产生缓冲区溢出的问题。

字符串对象的内部编码(encoding)有3种:intrawembstr
image.png
如果一个字符串保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成long),并将字符串对象的编码设置为int
image.png
如果字符串对象保存的是一个字符串,且字符串的长度<=32字节(Redis 2.+版本),那么字符串对象将使用一个简单动态字符串SDS来保存这个字符串,并将对象的编码设置为embstrembstr编码是专门用于保存短字符串的一种优化编码方式:
image.png
如果字符串对象保存的是一个字符串,且字符串的长度>32字节(Redis 2.+版本),那么字符串对象将使用一个SDS来保存这个字符串,并将对象的编码设置为raw
image.png
注意,这里embstr编码和raw编码的边界在redis不同版本中是不一样的:

  • redis 2.+是32字节;
  • redis 3.0-4.0 是39字节;
  • redis 5.0 是44字节;

embstrraw都会用SDS来保存值,但是不同之处在于embstr会通过一次内存分配函数来分配一块连续的内存空间来保存redisObjectSDS,而raw编码会通过调用两次分配函数来分别分配两块空间保存redisObjectSDS
这样做的好处如下:

  • embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为1次。
  • 释放embstr编码的字符串对象同样只需调用一次内存释放函数。
  • 数据都放在一块连续的内存空间,可以更好利用CPU缓存提升性能。

但肯定也是有缺点的:

  • 如果字符串长度增加到需要重新分配内存时,整个redisObjectSDS都需要重新分配空间,所以**embstr**编码的字符串对象实际上是只读的,Redis没有为embstr编码的字符串对象编写任何相应的修改程序。当我们对embstr编码的字符串对象进行任何修改时,程序会先将其转换为raw,再进行修改。

常用命令

基本操作:

# 设置 key-value 类型的值
> SET name iq50
OK
# 根据 key 获得对应的 value
> GET name
"iq50"
# 判断某个 key 是否存在
> EXISTS name
(integer) 1
# 返回 key 所储存的字符串值的长度
> STRLEN name
(integer) 4
# 删除某个 key 对应的值
> DEL name
(integer) 1

批量设置:

# 批量设置 key-value 类型的值
> MSET key1 hello key2 world 
OK
# 批量获取多个 key 对应的 value
> MGET key1 key2 
1) "hello"
2) "world"

计数器(字符串的内容为整数时可用):

# 设置 key-value 类型的值
> SET number 0
OK
# 将 key 中储存的数字值增一
> INCR number
(integer) 1
# 将key中存储的数字值加 10
> INCRBY number 10
(integer) 11
# 将 key 中储存的数字值减一
> DECR number
(integer) 10
# 将key中存储的数字值键 10
> DECRBY number 10
(integer) 0

设置过期时间(默认为永不过期):

# 设置 key 在 60 秒后过期(该方法是针对已经存在的key设置过期时间)
> EXPIRE name  60 
(integer) 1
# 查看数据还有多久过期
> TTL name 
(integer) 51

#两种方式设置 key-value 类型的值,并设置该key的过期时间为 60> SET key  value EX 60
OK
> SETEX key  60 value
OK

不存在就插入:

# 不存在就插入(not exists)
>SETNX key value
(integer) 1

应用场景

  1. 缓存对象

使用String来缓存对象有两种方式:

  • 直接缓存对象的整个JSON,例:SET user:1 '{"name":"iq50", "age":24}'
  • 基于key将对象的属性分离,例:MSET user:1:name iq50 user:1:age 24
  1. 进行计数

因为Redis处理命令是单线程,所以命令的执行过程是原子的。因此String数据类型适合计数的场景,比如计算访问次数、点赞、转发、库存数量等。
例:计算文章的阅读数量

# 初始化编号为108的文章的阅读量
> SET aritcle:No.108:readcount 0
OK
#阅读量+1
> INCR aritcle:No.108:readcount
(integer) 1
#阅读量+1
> INCR aritcle:No.108:readcount
(integer) 2
#阅读量+1
> INCR aritcle:No.108:readcount
(integer) 3
# 获取对应文章的阅读量
> GET aritcle:No.108:readcount
"3"
  1. 分布式锁

SET命令有个参数NX表示"key不存在才插入",可以借此实现分布式锁。

  • 如果key不存在,则显示插入成功,可以表示加锁成功;
  • 如果key存在,则显示插入失败,表示加锁失败;

一般而言,还会对分布式锁加上过期时间,命令如下:

SET lock_key unique_value NX PX 10000

# lock_key就是key键
# unique_value是客户端生成的唯一标识
# NX表示只有key不存在,才进行插入
# PX表示过期时间,10*10000ms=10s,这是为了避免客户端异常而无法正常释放锁

而解锁的过程就是将key键lock_key删除,但是要保证执行删除的客户端就是加锁的那个客户端,所以解锁的时候我们要借助unique_value判断是否就是加锁的那个客户端,是的话才允许删除lock_key。

  1. 共享Session信息

作为服务端,通常会使用Session来保存用户的会话状态,但这只适合用于单服务器应用,如果是分布式系统将不适用。比如用户A的Session信息被存储在服务器A,但用户A第二次访问时被负载均衡调度到服务器B,B没有存储用户A的Session,就会出现重复登陆的问题。
因此可以借助Redis对这些Session信息进行统一的存储和管理,保证无论请求发到哪个服务器,服务器都会从同一Redis获取相关信息,解决了分布式系统中Session存储的问题。
image.png

列表List

介绍

List列表是简单的字符串列表,按照插入顺序排序,可以从头部或尾部向List列表添加元素(所以这是个双向链表)。列表的最大长度为2**32-1,也即每个列表支持超过40亿个元素。

内部实现

List类型的底层数据结构是由双向列表或压缩列表实现的:

  • 如果列表的元素个数<512个(默认值,可由list-max-ziplist-entries配置),列表每个元素的值都<64字节(默认值,可由list-max-ziplist-value配置),Redis会使用压缩列表作为List类型的底层数据结构。
  • 如果列表的元素不满足上面的条件,Redis会使用双向链表作为List底层的数据结构。

但是在Redis 3.2版本以后,List数据类型的底层数据结构只由quicklist实现了,替代了双向链表和压缩列表

常用命令

image.png

# 将一个或多个值value插入到key列表的表头(最左边),最后的值在最前面
LPUSH key value [value ...] 
# 将一个或多个值value插入到key列表的表尾(最右边)
RPUSH key value [value ...]
# 移除并返回key列表的头元素
LPOP key     
# 移除并返回key列表的尾元素
RPOP key 

# 返回列表key中指定区间内的元素,区间以偏移量start和stop指定,从0开始
LRANGE key start stop

# 从key列表表头弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
BLPOP key [key ...] timeout
# 从key列表表尾弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
BRPOP key [key ...] timeout

应用场景

  1. 消息队列(有缺陷的)

消息队列在存取消息时,必须满足三个需求,分别是消息保序处理重复的消息保证消息可靠性
Redis的List和Stream两种数据类型,就可以满足消息队列的这三个需求。这里先介绍List,后面讲到Stream时会再详解。
Q1:如何满足消息保序需求?
List本身就是按照FIFO先进先出的顺序进行存取,所以本身就满足消息的保序需求。List可以使用LPUSH+RPOP命令实现消息队列。
image.png
不过,在消费者读取数据时,有一个潜在的性能风险点。
在生产者往List中写入数据时,List并不会主动通知消费者有新消息写入,如果消费者想要及时处理消息,就需要在程序中不停进行RPOP命令(比如使用一个while循环)。这必然会导致消费者不必要的性能损失。
为了解决这个问题,Redis提供了BRPOP命令。BRPOP命令也称为阻塞式读取(Blocking Right POP),客户端在没有读取到队列数据时,自动阻塞,直到有新的数据写入队列,再开始读取新数据
Q2:如何处理重复的消息?
消费者要实现重复消息的判断,需要2个方面的需求:

  • 每个消息都有一个全局的ID。
  • 消费者要记录已经处理过的消息ID。当收到一条消息后,消费者程序就可以对比这个消息ID和已经处理过的消息ID。如果已经处理过,就不再处理了。

但是List并不会为每个消息生成ID号,所以我们需要自行为每个消息生成一个全局唯一ID。生成之后,再使用LPUSH把消息插入List,并在这个消息中包含全局唯一ID。
例如:

# 生成一条全局ID111000102、库存量99的消息插入消息队列
> LPUSH mq "111000102:stock:99"
(integer) 1

Q3:如何保证消息可靠性?
当消费者程序从List中读取一条消息后,List就不会再留存这条消息了。所以如果消费者程序在处理消息的过程出现了故障或宕机,就会导致消息没有处理完成,那么消费者程序再次启动后,就没法再次从List中读取消息了。
为了留存消息,List类型提供了BRPOPPUSH命令,这个命令的作用是让消费者程序从一个List中读取消息,同时,Redis会把这个消息再插入到另一个List(备份List)留存。这样一来,如果消费者程序读了消息但没能正常处理,等它重启后,就可以从备份List中重新读取消息并处理了。
至此,我们解答了List作为消息队列的三大需求的问题。总结一下:

  • 消息保序:使用LPUSH+RPOP;
  • 重复消息处理:生产者自行实现全局唯一ID;
  • 消息可靠性:使用BRPOPPUSH;

画个分割线,这一段的标题说用List作为消息队列是有缺陷的,具体是什么呢?
List不支持多个消费者消费同一条消息,因为一个消费者RPOP消息,List中就删除了此消息,无法被其他消费者再次消费。
要想实现多消费者消费同一条消息,就要将多个消费者构造成一个消费组,但遗憾的是List不支持这么做,不过在Redis 5.0以后,引入的Stream支持,后面我们会再详细讨论。

哈希Hash

介绍

Redis中的Hash是一个键值对field-value的映射表,特别适合用来存储对象。
Redis中String和Hash的区别如下:
image.png
可以看到,对于Redis而言,String是key–>value的,而Hash是key–>[{field1,value1}, {field2,value2},…{fieldN,valueN}]。

内部实现

Hash类型的底层数据结构是由压缩列表哈希表实现的:

  • 如果哈希类型元素个数小于512个(默认,可由hash-max-ziplist-entries设置),所有值小于64字节(默认,可由hash-max-ziplist-value设置)的话,Redis会使用压缩列表作为Hash底层数据结构。
  • 否则,Redis使用哈希表作为Hash底层数据结构。

在Redis 7.0中,压缩列表数据结构已经废弃,交由listpack数据结构实现

常用命令

# 存储一个哈希表key的键值
HSET key field value   
# 获取哈希表key对应的field键值
HGET key field

# 在一个哈希表key中存储多个键值对
HMSET key field value [field value...] 
# 批量获取哈希表key中多个field键值
HMGET key field [field ...]       
# 删除哈希表key中的field键值
HDEL key field [field ...]    

# 返回哈希表key中field的数量
HLEN key       
# 返回哈希表key中所有的键值
HGETALL key 

# 为哈希表key中field键的值加上增量n
HINCRBY key field n   

应用场景

  1. 缓存对象

Hash类型的_(key、field、value)的结构与对象的(id、属性、值)_结构类似,因此很适合拿来存储对象。例:存储用户信息:

# 存储一个哈希表uid:1的键值
> HMSET uid:1 name Tom age 15
2
# 存储一个哈希表uid:2的键值
> HMSET uid:2 name Jerry age 13
2
# 获取哈希表用户id为1中所有的键值
> HGETALL uid:1
1) "name"
2) "Tom"
3) "age"
4) "15"

image.png
那么和String对比,一般的对象用String存JSON就行了,如果是某些属性频繁变化的对象,考虑用Hash来存储。

  1. 购物车

用户id为key,商品id为field,商品数量为value,恰好构成购物车的要素。
image.png

  • 添加商品:HSET cart:用户id 商品id 1;
  • 添加数量:HINCRBY cart:用户id 商品id 1;
  • 商品总数:HLEN cart:用户id;
  • 删除商品:HDEL cart:用户id 商品id;
  • 获取购物车所有商品:HGETALL cart:用户id;

当然,这里只是拿到了商品的id,在回显商品具体信息的时候,还是要拿商品id去查数据库的。

集合Set

介绍

Set类型是一种无序集合,集合中的元素没有先后顺序(无序)但是都唯一,类似于Java中的HashSet。一个集合最多可以存储2**32-1个元素。我们可以基于Set轻易实现求交集、并集、差集的操作。
image.png

内部实现

Set类型的底层数据结构是哈希表整数集合实现的:

  • 如果集合中的元素都是整数且元素个数小于512,Redis会采用整数集合作为Set的底层数据结构;
  • 否则,Redis会使用哈希表;

常用命令

Set的基本操作:

# 往集合key中存入元素,元素存在则忽略,若key不存在则新建
SADD key member [member ...]
# 从集合key中删除元素
SREM key member [member ...] 
# 获取集合key中所有元素
SMEMBERS key
# 获取集合key中的元素个数
SCARD key

# 判断member元素是否存在于集合key中
SISMEMBER key member

# 从集合key中随机选出count个元素,元素不从key中删除
SRANDMEMBER key [count]
# 从集合key中随机选出count个元素,元素从key中删除
SPOP key [count]

Set运算操作:

# 交集运算
SINTER key [key ...]
# 将交集结果存入新集合destination中
SINTERSTORE destination key [key ...]

# 并集运算
SUNION key [key ...]
# 将并集结果存入新集合destination中
SUNIONSTORE destination key [key ...]

# 差集运算
SDIFF key [key ...]
# 将差集结果存入新集合destination中
SDIFFSTORE destination key [key ...]

应用场景

集合Set的主要特性就是:无序、不可重复、支持并交叉等。
但是有个注意点,Set求差集、并集、交集的计算复杂度较高,在数据量大的情况下,不建议做这些操作。在主从集群中,为了避免主库因为Set做这些操作导致阻塞,我们可以选择一个从库做这些运算,然后把结果返回主库。

  1. 点赞

Set可以保证一个用户只能点一个赞。例:文章的id作为key,用户的id作为value。

# uid:1 用户对文章 article:1 点赞
> SADD article:1 uid:1
(integer) 1
# uid:2 用户对文章 article:1 点赞
> SADD article:1 uid:2
(integer) 1
# uid:3 用户对文章 article:1 点赞
> SADD article:1 uid:3
(integer) 1

# uid:1取消了点赞
> SREM article:1 uid:1
(integer) 1

# 获取哪些人点赞了
> SMEMBERS article:1
1) "uid:3"
2) "uid:2"

# 统计点赞的数量
> SCARD article:1
(integer) 2
  1. 共同关注

Set可以做交集,因此可以用来计算共同关注的好友、公众号等。
用户id作为key,关注的公众号id作为value。

# uid:1 用户关注公众号 id 为 56789
> SADD uid:1  5 6 7 8 9
(integer) 5
# uid:2  用户关注公众号 id 为 7891011
> SADD uid:2  7 8 9 10 11
(integer) 5

# 获取共同关注
> SINTER uid:1 uid:2
1) "7"
2) "8"
3) "9"

# 给uid:2推荐uid:1关注的公众号
> SDIFF uid:1 uid:2
1) "5"
2) "6"
  1. 抽奖活动

存储某活动中中奖的用户名 ,Set 类型因为有去重功能,可以保证同一个用户不会中奖两次。key为抽奖活动名,value为员工名称,把所有员工名称放入抽奖箱 :

>SADD lucky Tom Jerry John Sean Marry Lindy Sary Mark
(integer) 5

# 如果允许重复中奖(使用命令 SRANDMEMBER)
# 抽取 1 个一等奖:
> SRANDMEMBER lucky 1
1) "Tom"
# 抽取 2 个二等奖:
> SRANDMEMBER lucky 2
1) "Mark"
2) "Jerry"
# 抽取 3 个三等奖:
> SRANDMEMBER lucky 3
1) "Sary"
2) "Tom"
3) "Jerry"

# 如果不允许重复中奖(抽中后直接剔除,使用命令SPOP)
# 抽取一等奖1> SPOP lucky 1
1) "Sary"
# 抽取二等奖2> SPOP lucky 2
1) "Jerry"
2) "Mark"
# 抽取三等奖3> SPOP lucky 3
1) "John"
2) "Sean"
3) "Lindy"

有序集合Zset(也叫Sorted Set)

介绍

Zset类型相比于Set多了一个排序的属性score。Zset保留了Set不重复的特性(但是score可以重复)。
image.png

内部实现

Zset类型的底层数据结构是用压缩列表跳表实现的:

  • 如果Zset的元素个数小于128个,且每个元素值小于64字节,Redis使用压缩列表;
  • 否则,使用跳表;

在Redis 7.0中,废弃了压缩列表,改用listpack数据结构。

常用命令

Zset常用操作:

# 往有序集合key中加入带分值元素
ZADD key score member [[score member]...]   
# 往有序集合key中删除元素
ZREM key member [member...]                 
# 返回有序集合key中元素member的分值
ZSCORE key member
# 返回有序集合key中元素个数
ZCARD key 

# 为有序集合key中元素member的分值加上increment
ZINCRBY key increment member 

# 正序获取有序集合key从start下标到stop下标的元素
ZRANGE key start stop [WITHSCORES]
# 倒序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop [WITHSCORES]

# 返回有序集合中指定分数区间内的成员,分数由低到高排序。
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

# 返回指定成员区间内的成员,按字典正序排列, 分数必须相同。
ZRANGEBYLEX key min max [LIMIT offset count]
# 返回指定成员区间内的成员,按字典倒序排列, 分数必须相同
ZREVRANGEBYLEX key max min [LIMIT offset count]

Zset运算操作(相比Set,不支持差集):

# 并集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
ZUNIONSTORE destkey numberkeys key [key...] 
# 交集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
ZINTERSTORE destkey numberkeys key [key...]

应用场景

  1. 排行榜

有序集合的典型场景就是排行榜,以博客点赞为例:

# arcticle:1 文章获得了200个赞
> ZADD user:iq50:ranking 200 arcticle:1
(integer) 1
# arcticle:2 文章获得了40个赞
> ZADD user:iq50:ranking 40 arcticle:2
(integer) 1
# arcticle:3 文章获得了100个赞
> ZADD user:iq50:ranking 100 arcticle:3
(integer) 1
# arcticle:4 文章获得了50个赞
> ZADD user:iq50:ranking 50 arcticle:4
(integer) 1
# arcticle:5 文章获得了150个赞
> ZADD user:iq50:ranking 150 arcticle:5
(integer) 1

文章新增一个赞,使用ZINCRBY命令:

> ZINCRBY user:iq50:ranking 1 arcticle:4
"51"

查看某篇文章的赞:

> ZSCORE user:iq50:ranking arcticle:4
"50"

获取文章赞数最多的 3 篇文章,可以使用 ZREVRANGE 命令(倒序获取有序集合 key 从start下标到stop下标的元素):

# WITHSCORES 表示把 score 也显示出来
> ZREVRANGE user:iq50:ranking 0 2 WITHSCORES
1) "arcticle:1"
2) "200"
3) "arcticle:5"
4) "150"
5) "arcticle:3"
6) "100"

获取 100 赞到 200 赞的文章,可以使用 ZRANGEBYSCORE 命令(返回有序集合中指定分数区间内的成员,分数由低到高排序):

> ZRANGEBYSCORE user:iq50:ranking 100 200 WITHSCORES
1) "arcticle:3"
2) "100"
3) "arcticle:5"
4) "150"
5) "arcticle:1"
6) "200"
  1. 电话、姓名排序

使用有序集合的 ZRANGEBYLEXZREVRANGEBYLEX 可以帮助我们实现电话号码或姓名的排序,我们以 ZRANGEBYLEX (返回指定成员区间内的成员,按 key 正序排列,分数必须相同)为例。

# 插入一些号码
> ZADD phone 0 13100111100 0 13110114300 0 13132110901 
(integer) 3
> ZADD phone 0 13200111100 0 13210414300 0 13252110901 
(integer) 3
> ZADD phone 0 13300111100 0 13310414300 0 13352110901 
(integer) 3

# 获取所有号码
> ZRANGEBYLEX phone - +
1) "13100111100"
2) "13110114300"
3) "13132110901"
4) "13200111100"
5) "13210414300"
6) "13252110901"
7) "13300111100"
8) "13310414300"
9) "13352110901"

# 获取[132,133)开头的号码
> ZRANGEBYLEX phone [132 (133
1) "13200111100"
2) "13210414300"
3) "13252110901"

同理,姓名排序:

> zadd names 0 Toumas 0 Jake 0 Bluetuo 0 Gaodeng 0 Aimini 0 Aidehua 
(integer) 6

> ZRANGEBYLEX names - +
1) "Aidehua"
2) "Aimini"
3) "Bluetuo"
4) "Gaodeng"
5) "Jake"
6) "Toumas"

> ZRANGEBYLEX names [A (B
1) "Aidehua"
2) "Aimini"

> ZRANGEBYLEX names [C [Z
1) "Gaodeng"
2) "Jake"
3) "Toumas"

总结

本篇博客我们详细介绍了Redis中的5种基本数据类型:String、List、Hash、Set、Zset,以及其相关的底层实现、相关命令和使用场景等细节。 下一篇将详细介绍剩下的4种高级数据类型,敬请期待。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/485376.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分类预测 | Matlab实现CNN-LSTM-Mutilhead-Attention卷积神经网络-长短期记忆网络融合多头注意力机制多特征分类预测

分类预测 | Matlab实现CNN-LSTM-Mutilhead-Attention卷积神经网络-长短期记忆网络融合多头注意力机制多特征分类预测 目录 分类预测 | Matlab实现CNN-LSTM-Mutilhead-Attention卷积神经网络-长短期记忆网络融合多头注意力机制多特征分类预测分类效果基本介绍模型描述程序设计参…

Springboot做分组校验

目录 分组校验 Insert分组 Upload分组 测试接口 测试结果 添加测试 更新测试 顺序校验GroupSequence 自定义分组校验 自定义分组表单 CustomSequenceProvider 测试接口 测试结果 Type类型为A Type类型为B 总结&#xff1a; 前文提到了做自定义的校验注解&#xff…

牛客NC170 最长不含重复字符的子字符串【高频 中等 map、滑动窗口 Java,Go,PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/48d2ff79b8564c40a50fa79f9d5fa9c7 思路 用一个hashmap记录每个字母的index如果这个字母已经在map里了说明已经有重复了这样就更新看这个字母上次出现的index需要注意的是这种情况&#xff1a;“bacbca”这里的a…

初识kafka-数据存储篇1

目录 背景 1 kafka总体体系结构 2 疑问解答 2.1 高吞吐低延迟 2.2 实现分布式存储和数据读取 2.3 如何保证数据不丢失 背景 最近在和产品过项目审批的时候&#xff0c;深刻感受到业务方对系统的时时响应提出了更高的要求。目前手上大部分的业务都是基础定时任务去实现的&…

【yolo算法水果新鲜程度检测】

Yolo&#xff08;You Only Look Once&#xff09;系列算法是一类流行的一阶段实时目标检测模型&#xff0c;在水果检测领域有着广泛的应用。因其高效性和实时性而受到青睐&#xff0c;可用于识别和定位图像中不同种类的水果以及水果的新鲜度。 YOLOv3 已被用于水果商品的检测分…

家乡特色推荐系统设计与实现|SpringBoot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;…

【Linux】线程互斥{线程间的互斥相关背景概念/锁的相关问题/锁的原理/可重入VS线程安全}

文章目录 0.计算机如何完成y a * b c &#xff1f;1.线程间的互斥相关背景概念2.pthread_mutex_t3.pthread_mutex_lock()4.time() or gettimeofday5.锁的相关问题6.锁的原理7.可重入VS线程安全8.完善后的代码 0.计算机如何完成y a * b c &#xff1f; 来源&#xff1a; 王道…

nodejs+vue反诈科普平台的设计与实现pythonflask-django-php

相比于以前的传统手工管理方式&#xff0c;智能化的管理方式可以大幅降低反诈科普平台的运营人员成本&#xff0c;实现了反诈科普平台的标准化、制度化、程序化的管理&#xff0c;有效地防止了反诈科普平台的随意管理&#xff0c;提高了信息的处理速度和精确度&#xff0c;能够…

基础篇Redis

基础篇Redis 1.Redis简单介绍 Redis是一种键值型的NoSql数据库&#xff0c;这里有两个关键字&#xff1a; 键值型NoSql 其中键值型&#xff0c;是指Redis中存储的数据都是以key.value对的形式存储&#xff0c;而value的形式多种多样&#xff0c;可以是字符串.数值.甚至json…

Windows/Linux-openEuler系统使用路由侠内网穿透,部署项目详细教程

文章目录 Windows/Linux-openEuler系统使用路由侠内网穿透&#xff0c;部署项目详细教程一、在windows系统下载安装路由侠并实现项目部署1、下载路由侠并注册安装到Windows系统2、点击内网映射&#xff0c;添加映射&#xff0c;注册域名前缀3、选择网站应用4、配置你想要代理项…

mysql 存储引擎 基本介绍

目录 一 存储引擎概念介绍 &#xff08;一&#xff09;存储引擎概念 &#xff08;二&#xff09;MySQL常用的存储引擎 &#xff08;三&#xff09;存储引擎运作方式 二 MyISAM 存储引擎介绍 &#xff08;一&#xff09; MyISAM 存储引擎特点 1&#xff0c;不支持…

栅格地图路径规划:基于螳螂搜索算法(Mantis Search Algorithm,MSA)的机器人路径规划(提供MATLAB代码)

一、机器人路径规划介绍 移动机器人&#xff08;Mobile robot&#xff0c;MR&#xff09;的路径规划是 移动机器人研究的重要分支之&#xff0c;是对其进行控制的基础。根据环境信息的已知程度不同&#xff0c;路径规划分为基于环境信息已知的全局路径规划和基于环境信息未知或…

数据分析和机器学习库Pandas的使用

Pandas 库是一个免费、开源的第三方 Python 库&#xff0c;是 Python 数据分析和机器学习的工具之一。Pandas 提供了两种数据结构&#xff0c;分别是 Series&#xff08;一维数组结构&#xff09;与 DataFrame&#xff08;二维数组结构&#xff09;&#xff0c;极大地增强的了 …

个人博客系列-后端项目-系统角色配置(8)

系统角色配置需要设置的接口 用户可以绑定多个角色&#xff0c;角色对应有多个路由权限。用户绑定角色后&#xff0c;可以访问当前角色下的各个api路由和菜单路由。 用户注册时设置用户角色修改用户角色&#xff08;同时对应用户可以访问的路由将会同步变更&#xff09;添加修…

有关AI的随笔(1)

随笔&#xff1a; 今天是周天&#xff0c;是个好日子&#xff0c;结果老师布置的诗还没写&#xff0c;只好去借助AI&#xff0c;结果我发现了几个有趣的问题&#xff1a; 1. AI写的诗是如何来的&#xff1f;通过数据库&#xff1f; 2. 它真的明白是什么意思吗&#xff1f;&…

AutoDL算力云进行yolov5训练流程

目录 第一步 充值第二步 选择我们用到的显卡第三步 将我们的yolov5源代码导入服务器第四步 激活环境第五步 训练第六步 训练完成 提取 第一步 充值 打开我们的算力云官网 然后找到充值入口 最低充值50 第二步 选择我们用到的显卡 一般呢我都用便宜的2080ti 选择2080ti之后 基…

前端学习之用css和html做一个仿淘宝的导航栏

代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>仿淘宝界面案例</title><style>/* 最外层盒子 */.container{width: 270px;height: 385px;border: 1px solid rgb(255, 208, 0);bord…

【jvm】ParNew和ParallelOld为什么不能一起使用

java垃圾回收器ParNew和ParallelOld为什么不能一起使用 Java垃圾回收器中的ParNew和ParallelOld不能一起使用的原因在于它们的设计和目标不同&#xff0c;以及它们所属的垃圾回收器系列不同。 设计和目标差异&#xff1a; ParNew 收集器是 Serial 收集器的并行版本&#xff0c…

【计算机网络】物理层

文章目录 第二章 物理层一、 物理层的基本概念1. 物理层接口特性 二、数据通信基础1. 典型的数据通信模型2. 数据通信相关术语3. 设计数据通信系统要考虑的3个问题4. 三种通信方式5. 串行传输&并行传输6. 同步传输&异步传输7. 码元8. 数字通信系统数据传输速率的两种表…

Python入门(六)

参数传递 1.普通传参 通过判断对应位置来传递。 2.关键字传参 用关键字(Keyword&#xff09;的方式来传递参数。在定义函数时&#xff0c;我们给了形参一个符号标记&#xff0c;即参数名。关键字传递是根据参数名来让数据与符号对应上。因此&#xff0c;如果在调用时使用关键…