1、redis经典五种数据类型介绍及落地运用

1、官网地址

  1. 官网地址:Redis
  2. 中文官网地址:CRUG网站

2、Redis命令大全

官网命令大全

直接搜索即可

注:命令不区分大小写,而key是区分大小写的,可使用 help @类型名词 查看

3、Redis 基本数据类型

以前是 5 种数据类型,现在是 8 种啦~

  1. String(字符类型)
  2. Hash(散列类型)
  3. List(列表类型)
  4. Set(集合类型)
  5. SortedSet(有序集合类型,简称zset)
  6. Bitmap(位图)
  7. HyperLogLog(统计)
  8. GEO(地理)

3.1、string

3.1.1、获取/设置单个值

设置:SET key value

获取:SET key

127.0.0.1:6379> set name Bob
OK
127.0.0.1:6379> get name
"Bob"

3.1.2、同时设置/获取多个键值

设置:MSET key value [key value ....]

获取:MGET key [key ....]

127.0.0.1:6379> mset sex male score 11
OK
127.0.0.1:6379> mget sex score
1) "male"
2) "11"

3.1.3、数值增减

递增数字:INCR key

增加指定的整数:INCRBY key increment

递减数值:DECR key

减少指定的整数:DECRBY key decrement

127.0.0.1:6379> set count 0
OK
127.0.0.1:6379> incr count
(integer) 1
127.0.0.1:6379> incrby count 2
(integer) 3
127.0.0.1:6379> decr count
(integer) 2
127.0.0.1:6379> decrby count 2
(integer) 0

3.1.4、获取字符串长度

字符串长度:STRLEN key

127.0.0.1:6379> strlen name
(integer) 3
127.0.0.1:6379> get name
"Bob"
127.0.0.1:6379> strlen count
(integer) 1
127.0.0.1:6379> strlen score
(integer) 2

3.1.5、分布式锁

设置分布式锁:set key value [Ex seconds][PX milliseconds][NX|XX]

参数解释:

EX:key 在多少秒之后过期

PX:key 在多少毫秒之后过期

NX:当 key 不存在的时候,才创建 key,效果等同于setnx key value

XX:当 key 存在的时候,覆盖 key

3.1.6、应用场景

1、商品编号、订单号采用 INCR 命令生成

127.0.0.1:6379> incr item:1
(integer) 1
127.0.0.1:6379> incr item:1
(integer) 2
127.0.0.1:6379> incr item:1
(integer) 3

2、文章阅读量、点赞数和在看数

3.2、hash

3.2.1、获取/设置单个字段值

redis 中的 hash 类似于 java 中的 Map<String,Map<Object,Object>> 数据结构,即以字符串为 key,以 Map 对象为 value

添加一个 hash 对象:HSET key field value

获取 hash 对象的字段值:HGET key field

127.0.0.1:6379> hset person:bob age 18
(integer) 1
127.0.0.1:6379> hget person:bob age
"18"

3.2.2、同时设置/获取多个字段值

添加多个 hash 对象:HMSET key field value [field value ...]

获取多个 hash 对象的字段值:HMGET key field [field ....]

127.0.0.1:6379> hmset person:bob name bob score 91
OK
127.0.0.1:6379> hmget person:bob name score age
1) "bob"
2) "91"
3) "18"

3.2.3、获取所有字段值

获取 key 所对应所有的 hash 对象:HGETALL key

127.0.0.1:6379> hgetall person:bob
1) "age"
2) "18"
3) "name"
4) "bob"
5) "score"
6) "91"

3.2.4、获取某个 key 内的全部数量

获取 key 对应的所有 hash 对象个数:HLEN key

127.0.0.1:6379> hlen person:bob
(integer) 3

3.2.5、判断是否存在

判断字段名为 field 的 hash 对象是否存在:HEXISTS key field

127.0.0.1:6379> hexists person:bob age
(integer) 1
127.0.0.1:6379> hexists person:bob name
(integer) 1
127.0.0.1:6379> hexists person:bob gg
(integer) 0

3.2.6、删除一个 key

删除一个 hash 对象:HDEL key

127.0.0.1:6379> hdel person:bob name
(integer) 1
127.0.0.1:6379> hexists person:bob name
(integer) 0
127.0.0.1:6379> hgetall person:bob
1) "age"
2) "18"
3) "score"
4) "91"

3.2.7、数值增减

如何 key 和 field 不存在,则初始值为 0,否则在之前的数值上递增:HINCRBY key field increment

127.0.0.1:6379> hincrby person heygo 1
(integer) 1
127.0.0.1:6379> hincrby person heygo 1
(integer) 2
127.0.0.1:6379> hincrby person heygo 1
(integer) 3
127.0.0.1:6379> hget person heygo
"3"

3.2.8、应用场景

购物车早期版本,可在小中厂项目中使用

  1. 新增商品:hset shopcar:uid1024 334488 1
  2. 新增商品:hset shopcar:uid1024 334477 1
  3. 增加商品数量:hincrby shopcar:uid1024 334477 1
  4. 商品总数:hlen shopcar:uid1024
  5. 全部选择:hgetall shopcar:uid1024

3.3、list

与其说 list 是个集合,还不如说 list 是个双端队列

3.3.1、添加元素 & 查看列表

  1. 向 list 左边添加元素,如果 list 不存在则创建该 list:LPUSH key value [value ...]
  2. 向 list 右边添加元素,如果 list 不存在则创建该 list:RPUSH key value [value ....]
  3. 查看 list 中包含的元素:LRANGE key start stop,注:LRANGE key 0 -1 表示查看 list 中所有的元素

127.0.0.1:6379> lpush language c java
(integer) 2
127.0.0.1:6379> rpush language go
(integer) 3
127.0.0.1:6379> lrange language 0 -1
1) "java"
2) "c"
3) "go"

3.3.2、删除元素

  1. 从左边出队:LPOP key
  2. 从右边出队:RPOP key

127.0.0.1:6379> lpop language
"java"
127.0.0.1:6379> lrange language 0 -1
1) "c"
2) "go"
127.0.0.1:6379> rpop language
"go"
127.0.0.1:6379> lrange language 0 -1
1) "c"

3.3.3、获取列表中元素的个数

查看 list 中包含几个元素:LLEN key

127.0.0.1:6379> llen language
(integer) 1

3.3.4、应用场景

微信文章订阅公众号

  1. 比如我订阅了如下两个公众号,他们发布了两篇文章,文章 ID 分别为 666 和 888,可以通过执行 LPUSH likearticle:onebyId 666 888 命令推送给我
  2. 查看我自己的号订阅的全部文章,类似分页,下面0~10就是一次显示10条:LRANGE likearticle:onebyId 0 10

3.4、set

3.4.1、添加元素 & 删除元素 & 查看元素

  1. 向 set 中添加一个元素:SADD key member[member ...]
  2. 删除 set 中的指定元素:SREM key member [member ...]
  3. 获取 set 中的所有元素:SMEMBERS key

127.0.0.1:6379> sadd sid 1 1 2 2 3 4 5 6 7
(integer) 7
127.0.0.1:6379> smembers sid
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
127.0.0.1:6379> srem sid 7 8
(integer) 1
127.0.0.1:6379> smembers sid
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"

3.4.2、判断元素是否在集合中

判断指定元素是否在 set 中:SISMEMBER key member

127.0.0.1:6379> smembers sid
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
127.0.0.1:6379> sismember sid 2
(integer) 1
127.0.0.1:6379> sismember sid 7
(integer) 0

3.4.3、获取集合中的元素个数

获取 set 中元素的个数:SCARD key

127.0.0.1:6379> scard sid
(integer) 6

3.4.4、从集合中随机弹出元素

  1. 从集合中随机弹出元素,元素不删除:SRANDMEMBER key [数字]
  2. 从集合中随机弹出一个元素,出几个删几个:SPOP key[数字]

127.0.0.1:6379> srandmember sid
"6"
127.0.0.1:6379> srandmember sid 2
1) "6"
2) "3"
127.0.0.1:6379> srandmember sid 
"2"
127.0.0.1:6379> spop sid
"3"
127.0.0.1:6379> spop sid 2
1) "1"
2) "2"
127.0.0.1:6379> scard sid
(integer) 3

3.4.5、集合运算

  1. 集合的差集运算A-B:SDIFF key [key ...],属于A但不属于B的元素构成的集合
  2. 集合的交集运算A∩B:SINTER key [key ...],属于A同时也属于B的共同拥有的元素构成的集合
  3. 集合的并集运算AUB:SUNION key [key ...],属于A或者属于B的元素合并后的集合

127.0.0.1:6379> sadd set1 1 2 3 4 5 6 7 8
(integer) 8
127.0.0.1:6379> sadd set2 3 4 5 6 9
(integer) 5
127.0.0.1:6379> sdiff set1 set2
1) "1"
2) "2"
3) "7"
4) "8"
127.0.0.1:6379> sinter set1 set2
1) "3"
2) "4"
3) "5"
4) "6"
127.0.0.1:6379> sunion set1 set2
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
9) "9"

3.4.6、应用场景

1、微信抽奖小程序

  1. 如果某个用户点击了立即参与按钮,则执行 sadd key useId 命令将该用户 ID 添加至 set 中
  2. 显示已经有多少人参与了抽奖:SCARD key
  3. 抽奖(从set中任意选取N个中奖人)

随机抽奖2个人,元素不删除:SRANDMEMBER key 2

随机抽奖3个人,元素会删除:SPOP key 3

2、微信朋友圈点赞

  1. 新增点赞:SADD pub:msgID 点赞用户ID1 点赞用户ID2
  2. 取消点赞:SREM pub:msgID 点赞用户ID
  3. 展现所有点赞过的用户:SMEMBERS pub:msgID
  4. 点赞用户数统计,就是常见的点赞红色数字:SCARD pub.msgID
  5. 判断某个朋友是否对楼主点赞过:SISMEMBER pub:msgID 用户ID

3、Bilibili 共同关注的好友

共同关注的好友:SINTER 我关注的人 Ta关注的人

4、QQ内推可能认识的人

QQ 共同好友:SINTER 我的好友 Ta的好友

3.4、zset

形象理解 zset:向有序集合中加入一个元素和该元素的分数

3.4.1、添加元素 & 删除元素 & 获取元素

  1. 向 zset 中添加一个带分数(权值)的元素:ZADD key score member [score member ...]
  2. 删除 zset 中的指定元素:ZREM key member [member ...]
  3. 返回索引从start到stop之间的所有元素,并按照元素分数从小到大的顺序:ZRANGE key start stop [WITHSCORES],注:如果想要获取所有元素并且从小到大排序,可写为 ZRANGE key 0 -1

127.0.0.1:6379> zadd course  98 go 96 c 90 java
(integer) 3
127.0.0.1:6379> zrange course 0 -1
1) "java"
2) "c"
3) "go"
127.0.0.1:6379> zadd course 100 java
(integer) 0
127.0.0.1:6379> zrange course 0 -1
1) "c"
2) "go"
3) "java"
127.0.0.1:6379> zrem course java
(integer) 1
127.0.0.1:6379> zrange course 0 -1
1) "c"
2) "go"

3.4.2、获取元素的分数

127.0.0.1:6379> zscore course c
"96"
127.0.0.1:6379> zscore course java
(nil)
127.0.0.1:6379> zscore course go
"98"

3.4.3、获取指定分数范围的元素

获取指定分数范围的元素:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

127.0.0.1:6379> zadd course 91 java 67 python 99 mysql 78 net
(integer) 4
127.0.0.1:6379> zrange course 0 -1 withscores
 1) "python"
 2) "67"
 3) "net"
 4) "78"
 5) "java"
 6) "91"
 7) "c"
 8) "96"
 9) "go"
10) "98"
11) "mysql"
12) "99"
127.0.0.1:6379> zrangebyscore course 90 100
1) "java"
2) "c"
3) "go"
4) "mysql"

3.4.4、获取集合中元素的数量

获取集合中元素的数量:ZCARD key

127.0.0.1:6379> zcard course
(integer) 6

3.4.5、获得指定分数范围内的元素个数

获得指定分数范围内的元素个数:ZCOUNT key min max

127.0.0.1:6379> zcount course 90 100
(integer) 4

3.4.6、加某个元素的分数

增加某个元素的分数:ZINCRBY key increment member

127.0.0.1:6379> zrange course 0 -1 withscores
 1) "python"
 2) "67"
 3) "net"
 4) "78"
 5) "java"
 6) "91"
 7) "c"
 8) "96"
 9) "go"
10) "98"
11) "mysql"
12) "99"
127.0.0.1:6379> zincrby course 1 java
"92"

3.4.7、按照排名范围删除元素

按照排名范围删除元素:ZREMRANGEBYRANK key start stop

127.0.0.1:6379> zrange course 0 -1
1) "python"
2) "net"
3) "java"
4) "c"
5) "go"
6) "mysql"
127.0.0.1:6379> zremrangebyrank course 0 1
(integer) 2
127.0.0.1:6379> zrange course 0 -1
1) "java"
2) "c"
3) "go"
4) "mysql"

3.4.8、获取元素的排名

从小到大:ZRANK key member

从大到小:ZREVRANK key member

127.0.0.1:6379> zrange course 0 -1
1) "java"
2) "c"
3) "go"
4) "mysql"
127.0.0.1:6379> zrank course c
(integer) 1
127.0.0.1:6379> zrank course mysql
(integer) 3

3.4.9、应用场景

1、根据商品销售对商品进行排序显示

思路:定义商品销售排行榜(sorted set集合),key为goods:sellsort,分数为商品销售数量。

①商品编号1001的销量是9,商品编号1002的销量是15:ZADD goods:sellsort 9 1001 15 1002

②有一个客户又买了2件商品1001,商品编号1001销量加2:ZINCRBY goods:sellsort 2 1001

③求商品销量前10名:ZRANGE goods:sellsort 0 10 WITHSCORES

2、抖音热搜

①点击视频增加播放量:ZINCRBY hotvcr:20200919 1八佰,ZINCRBY hotvcr:20200919 15 八佰 2 花木兰

②展示当日排行前10条:ZREVRANGE hotvcr:20200919 0 9 WITHSCORES

3.5、redis 事务相关命令

事务介绍

  1. Redis的事务是通过MULTl,EXEC,DISCARD和WATCH这四个命令来完成。
  2. Redis的单个命令都是原子性的,所以这里确保事务性的对象是命令集合。
  3. Redis将命令集合序列化并确保处于一事务的命令集合连续且不被打断的执行。
  4. Redis不支持回滚的操作。

3.5.1、MULTI

  1. 用于标记事务块的开始。
  2. Redis会将后续的命令逐个放入队列中,然后使用EXEC命令原子化地执行这个命令序列。
  3. 语法:MULTI

3.5.2、EXEC

  1. 在一个事务中执行所有先前放入队列的命令,然后恢复正常的连接状态。
  2. 语法:EXEC

3.5.3、DISCARD

  1. 清除所有先前在一个事务中放入队列的命令,然后恢复正常的连接状态。
  2. 语法:DISCARD

3.5.4、WATCH

  1. 当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的状态。
  2. 语法:WATCH key[key..…]注:该命令可以实现redis的乐观锁

3.5.5、UNWATCH

  1. 清除所有先前为一个事务监控的键。
  2. 语法:UNWATCH

3.5.6、演示事务的使用

ClienA 使用 MULTI 命令开启一个事务,并执行 SET lock 1 命令,当开启事务之后,执行命令返回 QUEUED,表示已经将该命令压入队列

ClientB 执行 SET lock 2 命令将 lock 的值设置为 2

ClientA 执行 EXEC 命令提交事务,执行 get lock 命令获取到 lock 的值为 1(ClientB 虽然加塞,但不影响 ClientA 的事务,但这不是我们想要的效果)

3.5.7、演示 WATCH 的使用

ClientA 先使用 WATCH lock 命令见监视 lock 这把锁,然后使用 MULTI 命令开启事务修改 lock 的值

ClientB 执行 set lock 4 命令将 lock 的值修改为 4

ClientA 执行 EXEC 命令提交事务,发现命令的返回值为 nil,表示事务执行失败,使用 GET lock 获取 lock 变量的值为 4(修改失败),WATCH 之后记得使用 UNWATCH 解除监视

4、Redis 缓存淘汰策略

4.1、Redis 缓存淘汰策略相关的面试题

  1. 生产上你们的redis内存设置多少?
  2. 如何配置、修改redis的内存大小?
  3. 如果内存满了你怎么办?
  4. redis 清理内存的方式?定期删除和惰性删除了解过吗
  5. redis 的缓存淘汰策略
  6. redis 的 LRU 淘汰机制了解过吗?可否手写一个 LRU 算法

4.2、redis 内存满了怎么办

4.2.1、如何查看 redis 最大占用内存

在 redis.conf 配置文件中有一个,输入 :set nu 显示行号,大约在 859行有一个 maxmemory 字段,用预设值 redis 的最大占用内存

4.2.2、redis 会占用物理机器多少内存?

如果不设置最大内存大小或者设置最大内存大小为 0,在 64 位操作系统下不限制内存大小,在32位操作系统下最多使用 3GB 内存

4.2.3、一般生产上如何配置 redis 的内存

一般推荐Redis设置内存为最大物理内存的四分之三,也就是 0.75

4.2.4、如何修改 redis 内存设置

1、通过修改文件配置(永久生效):修改 maxmemory 字段,单位为字节

2、通过命令修改(重启失效):config set maxmemory 104857600 设置 redis 最大占用内存为 100MB,config get maxmemory 获取 redis 最大占用内存

4.2.5、通过命令查看 redis 内存使用情况?

通过 info 指令可以查看 redis 内存使用情况:used_memory_human 表示实际已经占用的内存,maxmemory 表示 redis 最大占用内存

4.2.5、如果把 redis 内存打满了会发生什么? 如果 redis 内存使用超出了设置的最大值会怎样?

修改配置,故意把最大内存设置为 1byte,再通过 set k1 v1 命令下 redis 中写入数据,redis 将会报错:(error) OOM command not allowed when used memory > ‘maxmemory’

4.2.6、总结 & 结论

如果设置了 maxmemory 的选项,假如 redis 内存使用达到上限,并且 key 都没有加上过期时间,就会导致数据写爆 redis 内存。

为了避免类似情况,于是引出下一部分的内存淘汰策略

4.3、redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现?

4.3.1、redis过期策略

redis 过期策略是:定期删除+惰性删除。

所谓定期删除,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。

假设 redis 里放了 10w 个 key,都设置了过期时间,你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key 上了。注意,这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的灾难。实际上 redis 是每隔 100ms 随机抽取一些 key 来检查和删除的。

但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。

获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。

但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整?

答案是:走内存淘汰机制。

4.3.2、内存淘汰机制

redis 内存淘汰机制有以下几个:

  • noeviction: (默认)当内存不足以容纳新写入数据时,新写入操作会报错。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
  • allkeys-lfu:对所有key使用LFU算法进行删除
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
  • volatile-lfu:对所有设置了过期时间的key使用LFU算法进行删除
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,删除马上要过期的key

总结

2个维度

①过期键中筛选

②所有键中筛选

4个方面

①lru

②lfu

③random

④ttl

如何配置 redis 的内存淘汰策略?

1.通过修改文件配置(永久生效):配置 maxmemory-policy 字段

2.通过命令修改(重启失效):config set maxmemory-policy allkeys-lru 命令设置内存淘汰策略,config get maxmemory-policy 命令获取当前采用的内存淘汰策略

4.3.3、redis LRU 算法

LRU 是 Least Recently Used 的缩写,即最近最少使用,是一种常用的页面置换算法,每次选择最近最久未使用的页面予以淘汰

[ LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小。]

leetcode : 力扣

实现 LRUCache 类:

LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存

int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。

void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?

输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lru-cache
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

4.3.3.1、LRU 算法设计思想

查找和插入的时间复杂度为 O(1),HashMap 没得跑了,但是 HashMap 是无序的集合,怎么样将其改造为有序集合呢?答案就是在各个 Node 节点之间增加 prev 指针和 next 指针,构成双向链表

LRU 的算法核心是哈希链表,本质就是 HashMap+DoubleLinkedList 时间复杂度是O(1),哈希表+双向链表的结合体,下面这幅动图完美诠释了 HashMap+DoubleLinkedList 的工作原理

4.3.3.2、LRU 算法编码实现

① 借助 JDK 自带的 LinkedHashMap

LinkedHashMap 的注释中写明了: LinkedHashMap 非常适合用来构建 LRU 缓存

通过继承 LinkedHashMap,重写 boolean removeEldestEntry(Map.Entry<K, V> eldest) 方法就完事了。。。

为何重写 boolean removeEldestEntry(Map.Entry<K, V> eldest) 方法 就实现LRU算法了呢,接下来我们来解读一下LinkedHashMap 的源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值