Redis学习笔记(面试+实战)

概念(面试)

1.什么是Redis

    Redis即远程字典服务,是一个开源的用c语言编写,基于内存,亦可持久化的key-value型非关系型数据库,并提供多种语言的API。

2.Redis的优缺点

优点:

  • 读写性能优异(读110000/s;写81000/s)
  • 支持数据持久化(AOF和RDB俩种持久化方式)
  • 支持事务
  • 数据结构丰富(string、hash、set、zset、list)
  • 支持主从复制

缺点:

  • 不适用于较大数据容量(物理内存的限制)
  • 不具备自动容错和恢复的功能(数据存在内存)
  • 存在数据不一致的问题(主机宕机)
  • 在线扩容困难

3.Redis为什么这么快

  • 操作完全基于内存
  • 数据结构简单
  • 单线程(避免了上下文切换和资源竞争)
  • 使用非阻塞IO

4.Redis的持久化

4.1 什么是Redis持久化

    持久化就是把内存中的数据写到磁盘中去,防止服务宕机内存消失。

4.2 Redis持久化机制

4.2.1RDB(快照)

    Redis默认的持久化方式,按照一定的时间将内存中的数据以快照的方式保存到硬盘。RDB持久化的方式分为自动触发和手动触发。

手动触发:
save命令和bgsave命令。save命令会阻塞当前redis服务器,线上环境不建议使用(已经废弃)。bgsave是对save的优化,Redis主进程会fork一个子进程,只有fork的这个过程会阻塞主进程,rdb持久化过程由子进程负责,不影响主进程继续处理客户端请求。

自动触发:
满足配置条件,如1秒内修改900个数据等,自动进行bgsave。
从节点全量复制主节点时,主节点自动进行bgsave。
执行shutdown命令时,若没有配置AOF持久化,自动进行bgsave。

优点:

  • 只产生一个dump.rdb文件
  • 容灾性好(一个文件可以保存到较安全的磁盘)
  • 性能最大化(子进程来完成写操作进行持久化,主进程不执行任何IO操作继续处理命令)
  • 比AOF更适合与数据集大的情况

缺点:

  • 数据安全性低(RDB是间隔时间进行持久化,容易丢失数据)
  • fork操作属于重量级操作,频繁执行成本过高
4.2.2AOF

    AOF持久化是将Redis执行的每次写命令记录到单独的日志文件中。重启redis再重新执行AOF文件达到恢复数据的目的。(RDB和AOF种方式同时开启时,Redis会优先选择AOF进行恢复)
优点:

  • 数据安全(记录每一次写操作)
  • 通过append模式写文件,即使文件出错,也可以使用redis-check-aof工具对aop文件进行修正。
  • AOF文件过大时,会对文件进行rewrite操作,合并相关命令,缩小文件体积。

缺点:

  • AOF文件比RDB文件大,且恢复速度慢。
  • 数据集大时,比rdb启动效率低
4.2.3如何选择合适的持久化方式
  • 一般来说,AOF和RDB结合使用。通过RDB避免AOF的bug。
  • 如果对数据严格性不高,可以只使用RDB持久化。
  • 如果只考虑数据完整性的情况,只使用AOF。

5.Redis的过期策略和内存淘汰机制

Redis过期策略:
假如我们适应expire设置了一批key在某一事件段后过期,当这些key到达过期时间后,并不是直接在内存中删除的,而是通过定时删除+惰性删除俩种方式来删除过期key的。所谓定时删除就是每隔100ms随机抽取一些过期key进行删除。对于一些到了过期时间没能及时删除的key使用惰性删除,所谓惰性删除就是你每次获取一个key时,redis都会对该key进行一次检查,若果该key已经失效就将其删除。对于一些定时删除漏掉的key,也不经常使用的这些key走淘汰机制。
Redis淘汰机制:
若果redis内存耗尽,会进行内存淘汰,有如下策略。

  • 内存耗尽,新写入数据直接报错
  • 移除最少使用的key
  • 随机删除某个key
  • 在设置了过期时间的key中,移除最少使用的key
  • 在设置了过期时间的key中,随机删除某个key
  • 在设置了过期时间的key中,删除最早过期的key

6.Redis集群

6.1主从模式

主从模式是三种模式中最简单的,在主从复制中,数据库分为两类:主数据库(master)和从数据库(slave)。

工作机制:

当slave启动后,主动向master发送SYNC命令。master接收到SYNC命令后在后台保存快照(RDB持久化)和缓存保存快照这段时间的命令,然后将保存的快照文件和缓存的命令发送给slave。slave接收到快照文件和命令后加载快照文件和缓存的执行命令。复制初始化后,master每次接收到的写命令都会同步发送给slave,保证主从数据一致性。
缺点:
从上面可以看出,master节点在主从模式中唯一,若master挂掉,则redis无法对外提供写服务。

6.2哨兵模式

主从模式的弊端就是不具备高可用性,当master挂掉以后,Redis将不能再对外提供写入操作,因此sentinel应运而生。
工作机制:

  • 每个sentinel以每秒钟一次的频率向它所知的master,slave以及其他sentinel实例发送一个 PING 命令
  • 如果一个实例距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被sentinel标记为主观下线。
  • 如果一个master被标记为主观下线,则正在监视这个master的所有sentinel要以每秒一次的频率确认master的确进入了主观下线状态
  • 当有足够数量的sentinel(大于等于配置文件指定的值)在指定的时间范围内确认master的确进入了主观下线状态, 则master会被标记为客观下线
  • 在一般情况下, 每个sentinel会以每 10 秒一次的频率向它已知的所有master,slave发送 INFO 命令
  • 当master被sentinel标记为客观下线时,sentinel向下线的master的所有slave发送 INFO 命令的频率会从 10 秒一次改为 1 秒一次
  • 若没有足够数量的sentinel同意master已经下线,master的客观下线状态就会被移除;
    若master重新向sentinel的 PING 命令返回有效回复,master的主观下线状态就会被移除

7. 缓存异常

7.1 缓存穿透

缓存穿透是指查询缓存和数据库中都没有的数据,导致大量请求都落到数据库上,使数据库短时间内承受大量请求而垮掉。
解决方案

  • 接口层做校验,例如id<=0的直接拦截
  • 将空数据缓存,设置较短的过期时间
  • 布隆过滤器
7.2 缓存击穿

缓存击穿是指对某一个key,在数据库中存在,在缓存中失效的情况下,同时有大量的请求由于在缓存中没有命中全部落到数据库上,将数据库打垮的情况。
解决方案

  • 设置热点数据永不过期
  • 加互斥锁
7.3 缓存雪崩

缓存击穿是针对某一热点key,缓存雪崩是大量的key在同一时间大面积失效,使得所有请求都落在数据库上,是数据库垮掉的情况。
解决方案

  • 不同数据的缓存过期时间设置成随机的。
  • 并发量较小情况,可以加锁排队。
    给缓存数据增加缓存失效标记,及时更新数据缓存。

实战

1.Redis安装(Linux)

1.1 下载安装包

wget http://download.redis.io/releases/redis-5.0.8.tar.gz

1.2 解压

 使用到的linux命令:
 1)

在这里插入图片描述
1.3 环境搭建
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1.4 redis的启动与测试

1.复制redis配置文件
1)mkdir myconfig
2)cp /opt/redis-5.0.8/redis.conf myconfig

在这里插入图片描述

2.修改redis配置文件,将redis启动方式改为后台启动(保姆级)
Linux命令 :
1) vim redis.conf
2) /daemon
3) i
4) 修改
5)按键盘esc
6):wq

在这里插入图片描述

3. 启动redis服务
使用到的linux命令:
1)cd ..
2)redis-server myconfig/redis.conf

在这里插入图片描述

4.redis客户端连接并测试
使用到的linux命令:
1)redis-cli -p 6379
redis操作:
1) set redis success
2) get redis
3) exit

在这里插入图片描述

2.Redis相关命令

2.1 常用基础命令
 ping  查看redis客户端连接服务端是否成功

在这里插入图片描述

keys * 查看当前数据库下的所有key

在这里插入图片描述

select number 切换数据库为number数据库(redis有16个数据库0-15)

在这里插入图片描述

flushdb  清空当前数据库中的数据

在这里插入图片描述

flushall  清空所有数据库中的数据

在这里插入图片描述

exit 退出redis客户端

在这里插入图片描述

2.2 Redis键(key)
DEL key  在key存在时删除key

在这里插入图片描述

EXISTS key  检查给定 key 是否存在

在这里插入图片描述

EXISTS key  检查给定 key 是否存在

在这里插入图片描述

MOVE key db 将当前数据库的 key 移动到给定的数据库 db 当中

在这里插入图片描述

TTL key  以秒为单位,返回给定 key 的剩余生存时间

在这里插入图片描述

RANDOMKEY 从当前数据库中随机返回一个 key

在这里插入图片描述

RENAME key newkey  修改 key 的名称

在这里插入图片描述

TYPE key  返回当前key存储值的类型

在这里插入图片描述

2.3 Redis 字符串(String)
SET key value  设置指定key的值
GET key  获取指定key的值

在这里插入图片描述

GETRANGE key start end  返回指定key的字符串值的子字符串(下标从start到end)
SETRANGE key offset value  用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。

在这里插入图片描述

MGET key1 [key2..]   同时获取多个key的值
MSET key value [key value ...]  同时给多个key设置value

在这里插入图片描述

SETEX key seconds value  设置key的value的同时设置这个key的过期时间
SETNX key value  只有当这个key不存在时,才会为该key设置value

在这里插入图片描述

INCR key  将key的数子值自增1
DECR key 将key的数字值自减1

在这里插入图片描述

INCRBY key n  给指定key的值自增n
DECRBY key n 给指定key的值自减n

在这里插入图片描述

STRLEN key  返回指定key所存储字符串的长度
APPEND key value  如果key已经存在,对原有key的value进行拼接;’
	              如果不存在,相当于set操作

在这里插入图片描述

getset key value 返回指定key原本的value,
					并用新value覆盖原本的value

在这里插入图片描述

2.4 Redis 哈希(Hash)

Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
Redis 中每个 hash 可以存储 2的32次方-减1个键值对(40多亿)
在这里插入图片描述

2.5 Redis 列表(List)

list : 列表中元素有序,可以重复。
1.头部为L操作相关

127.0.0.1:6379> LPUSHX list a  向已存在的列表头部添加元素
(integer) 0				由于list列表不存在,所以执行失败
127.0.0.1:6379> LPUSH list a  向列表头部添加一个或多个元素 
								若列表不存在则创建列表
(integer) 1
127.0.0.1:6379> LPUSHX list b 
(integer) 2		向已存在的列表b添加元素操作成功
127.0.0.1:6379> LRANGE list 0 -1 从左到右(从头到尾)输出列表所有元素
1) "b"
2) "a"
127.0.0.1:6379> LSET list 0 c  替换列表中指定位置元素的值
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "c"
2) "a"
127.0.0.1:6379> LPOP list  弹出列表头部元素
"c"
127.0.0.1:6379> LRANGE list 0 -1
1) "a"
127.0.0.1:6379> LPUSH list b c  
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> LTRIM list 0 1 对列表进行裁剪
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "c"
2) "b"
127.0.0.1:6379> Llen list  获取列表的长度
(integer) 2
127.0.0.1:6379> LINDEX list 0 通过索引获取列表中的某个元素
"c"

2.头部为R操作相关

127.0.0.1:6379> LRANGE list 0 -1
1) "c"
2) "b"
127.0.0.1:6379> RPUSH list a  向列表尾部添加元素
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> RPOP list  从列表尾部移除一个元素
"a"
127.0.0.1:6379> LRANGE list 0 -1
1) "c"
2) "b"
127.0.0.1:6379> RPUSHX list a  向已存在的列表尾部添加一个元素
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> 

3.B开头相关

127.0.0.1:6379> LRANGE list 0 -1
1) "b"
2) "a"
127.0.0.1:6379> LPUSH list2 1 2 3
(integer) 3
127.0.0.1:6379> BLPOP list2 list 10  每次弹出第一个列表的头部元素,
				直到该列表元素全部弹出,继续取下一个列表的元素,
				如果所有列表中都没有元素,则阻塞列表10s
1) "list2"
2) "3"
127.0.0.1:6379> BLPOP list2 list 10
1) "list2"
2) "2"
127.0.0.1:6379> BLPOP list2 list 10
1) "list2"
2) "1"
127.0.0.1:6379> BLPOP list2 list 10
1) "list"
2) "b"
127.0.0.1:6379> BLPOP list2 list 10
1) "list"
2) "a"
127.0.0.1:6379> BLPOP list2 list 10
(nil)
(10.07s)
127.0.0.1:6379> 
127.0.0.1:6379> LPUSH list1 a b c
(integer) 3
127.0.0.1:6379> LPUSH list2 1 2 3
(integer) 3
127.0.0.1:6379> BRPOP list1 list2 10  每次弹出第一个列表的尾部元素,
				直到该列表元素全部弹出,继续取下一个列表的元素,
				如果所有列表中都没有元素,则阻塞列表10s
1) "list1"
2) "a"

4.BRPOPLPUSH

BRPOPLPUSH source destination timeout
从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 
如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

在这里插入图片描述

2.6 Redis Set集合(Set)

set : 集合中元素不可以重复,无序。

  1. 单个set集合相关操作
    在这里插入图片描述

2.多个set集合之间的相关操作
在这里插入图片描述

2.7 Redis Zset

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.8 Redis HyperLogLog(基数统计)

    Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
    在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
    因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
在这里插入图片描述

2.9 Bitmap(位图)

可以运用与打卡等只有0 1俩种状态的场景
举例1:某一天用户的打卡状态
在这里插入图片描述
举例2:某个用户365天的打卡状态
在这里插入图片描述

3. Redis 事务

3.1事务相关概念

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务。

  • 命令入队。

  • 执行事务。

      DISCARD 取消事务,放弃执行事务块内的所有命令。
      		若存在key被watch监视,则取消对所有key的监视。
      
      EXEC 执行所有事务块内的命令,同时结束对所有key的监视。
    
      MULTI 标记一个事务块的开始。事务块内的多条命令会按照先后顺	
      		序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)
      		地执行。
      
      Watch 命令用于监视一个(或多个) key ,如果在事务执行之前这
      		个(或这些) key 被其他命令所改动,那么事务将被打断。
    
      Unwatch 命令用于取消 WATCH 命令对所有 key 的监视。
    
3.2案例1:事务正常执行

在这里插入图片描述

3.2案例2:用watch关键字实现乐观锁

什么是乐观锁?
    大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
在这里插入图片描述
不导致冲突情况:
第一步:A用户获取到数据,且数据的版本号为1
第二步:A用户对版本号加1,版本号变为2,对数据进行修改
第三步:用户A再次获取数据的版本号,并与2进行对比,若比2小则将修改后的数据写入,否则操作失败。
冲突情况:
第一步:A用户和B用户都获取到数据,且数据的版本号为1
第二步:A用户和B用户对版本号加1,版本号变为2,对数据进行修改
第三步:用户B再次获取数据的版本号为1,比版本号2小,将用户B修改后的数据写入
第四步:用户A再次获取数据的版本号为2,不小于版本号2,写入失败

不使用乐观锁

在这里插入图片描述

使用乐观锁

在这里插入图片描述

4. Redis配置文件详解

CONFIG GET 配置名称 获取指定名称的配置信息

在这里插入图片描述

CONFIG GET * 获取所有配置的配置信息

在这里插入图片描述

CONFIG SET timeout 500 设这指定名称的配置信息

在这里插入图片描述

配置项说明

在这里插入图片描述

5. Redis订阅发布

相关命令

在这里插入图片描述
第一步:开启3个客户端
在这里插入图片描述

第二步:客户端2,客户端3订阅maoyu这个频道
在这里插入图片描述
第三步:客户端1在maoyu这个频道发布消息
在这里插入图片描述

第四步:客户端2和客户端3收到客户端一发布的消息
在这里插入图片描述

6. SpringBoot整合Redis

6.1相关概念

redis的三种java客户端:Jedis,Redisson,Lettuce

  • Jedis:是老牌的Redis的Java实现客户端,提供了比较全面的Redis命令的支持,
  • Redisson:实现了分布式和可扩展的Java数据结构。
  • Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。

三者的比较

  • jedis使直接连接redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个jedis实例增加物理连接 ;
  • lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,StatefulRedisConnection是线程安全的,所以一个连接实例可以满足多线程环境下的并发访问,当然这也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
  • Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

总结:
优先使用Lettuce,如果需要分布式锁,分布式集合等分布式的高级特性,添加Redisson结合使用,因为Redisson本身对字符串的操作支持很差。

6.2 远程连接redis相关配置设置

vim redis.conf

第一步:

在这里插入图片描述
第二步:
在这里插入图片描述
第三步:防火墙放行6379端口
第四步:向服务器安全组中添加6379

6.3 Jedis客户端的使用

1.创建maven项目

在这里插入图片描述

2.pom.xml中引入jedis的坐标

在这里插入图片描述

3.测试连接

在这里插入图片描述

使用相关API

在这里插入图片描述

6.4 Lettuce的使用(SpringBoot2.x默认)

1.创建springboot项目

2.引入坐标
在这里插入图片描述
3.测试连接

@RunWith(SpringRunner.class)
@SpringBootTest
public class BootRedisApplicationTests {
@Autowired
RedisTemplate redisTemplate;
@Test
public void contextLoads() {
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
System.out.println(connection.ping());
System.out.println("========================");
}
}

在这里插入图片描述
4.

字符串相关操作

在这里插入图片描述

各类操作与redis中命令相对应

在这里插入图片描述

结束语句

更多高级的面试知识,分布式中redis的使用,以及spring整合redis中的更多细节,后续逐步更新。

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
ava实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),可运行高分资源 Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值