SpringBoot整合Redis及其持久化机制(二)

SpringBoot整合Redis及其持久化机制

  • 1、Redis全局命令
    • 1.2、Redis事务
  • 2、Redis持久化
    • 2.1、RDB方式
      • 2.1.1、客户端触发机制
      • 2.1.2、服务端触发机制
      • 2.2.3、配置生成快照名称和位置
      • 2.2.4、优点
      • 2.2.5、缺点
    • 2.2、AOF方式
      • 2.2.1、优点
      • 2.2.2、缺点
    • 2.3、RDB-AOF混合方式
    • 2.4、持久化机制的选择
  • 3、Java操作Redis
  • 4、SpringBoot整合Redis
    • 3.1、环境准备
    • 3.2、SpringBoot操作key
    • 3.3、SpringBoot操作String
    • 3.4、SpringBoot操作List
    • 3.5、SpringBoot操作Set
    • 3.6、SpringBoot操作ZSet
    • 3.7、SpringBoot操作Hash
    • 3.8、SpringBoot操作对象
    • 3.9、BoundAPI
    • 3.10、总结
  • 5、Redis的应用场景

  • 参考视频:适合后端编程人员的Redis实战教程、redis应用场景、分布式缓存、Session管理、面试相关

1、Redis全局命令

全局命令针对的是所有的key,常用的全局key命令如下:

命令格式功能案例
keys pattern按照pattern 匹配规则,列表redis中所有的keykeys xxx:*查询符合 xxx:* 格式的key
exists key判断key是否存在exists name判断 name 字段是否存在
expire key seconds给key设置过期时间,超时:secondsexpire name 10给 name 设置10s
persist key取消key过期时间persist name取消name 的过期时间
select index切换数据库,默认是第0个,共有【0,15】个select 0切换第0个数据库
move key db从当前数据库将key移动到指定db库move name 1将 name 移动到第1个数据库
randomkey随机返回一个keyrandomkey
rename key newkey将key改名为newkeyrename name newname将 name 改名为 newname
echo message打印message信息echo message
dbsize查看key个数dbsize
info查看redis数据库信息info
config get *查看所有redis配置信息config get *
flushdb清空当前数据库flushdb
flushall清空所有数据库flushall

1.2、Redis事务

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

  • 开始事务。
  • 命令入队。
  • 执行事务。

它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set name dafei
QUEUED
127.0.0.1:6379(TX)> set age 18
QUEUED
127.0.0.1:6379(TX)> incr age 
QUEUED
127.0.0.1:6379(TX)> incr name
QUEUED
127.0.0.1:6379(TX)> get age
QUEUED
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) (integer) 19
4) (error) ERR value is not an integer or out of range
5) "19"
6) "dafei"
127.0.0.1:6379> 

​ 单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。Redis事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

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

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

2、Redis持久化

先来一个小实验:

  1. 在Redis中添加2个key-value对

    127.0.0.1:6379> set aa aa
    OK
    127.0.0.1:6379> set bb bb
    OK
    127.0.0.1:6379> keys *
    # 获取redis中所有的key
    
  2. 重启 redis

    redis-server restart 
    
  3. 再次执行keys *

    keys *
    
  4. 分析结果:会出现如下结果

    • 之前的key在,aa bb 都在(最理想的结果)
    • 之前的key在,aa也在,bb不见了
    • 之前的key在,aa, bb 不在
    • 之前的key, aa, bb 都不在了(最坏的结果)

思考:为啥会这样?以我们对内存的操作理解,按道理重启之后数据应该全丢失了,为啥Redis 可能丢失,也可能不丢失,为何?这里就涉及到Redis的持久化机制了。Redis不定时将内存中的数据存到硬盘中

在这里插入图片描述

Redis持久化机制目前以后3种,分别为:

  1. 快照方式(RDB, Redis DataBase)

  2. 文件追加方式(AOF, Append Only File)

  3. 混合持久化方式(Redis4版本之后)

2.1、RDB方式

Snapshotting(快照)是持久化机制的默认方式,将内存数据中以快照的方式写入到二进制文件(硬盘)中,默认为dump.rdb。触发RDB持久化过程分手动触发与自动触发。

  • 客户端方式: BGSAVE 和 SAVE指令
  • 服务器配置自动触发

2.1.1、客户端触发机制

手动触发

  • 使用save命令:会阻塞当前Redis服务器,知道RDB过程完成为主,如果内存数据较多,会造成长时间阻塞,影响其他命令的使用,不建议轻易使用

  • 使用bgsave命令:Redis进程执行fork指令创建子进程,由子进程实现RDB持久化,有需要时建议使用bgsave命令。

自动触发

使用save相关配置,格式: save m n 表示m秒内数据集存在n次修改时会自动触发bgsave命令。

#900秒内如果超过1个Key被修改则发起快照保存
save 900 1  

#300秒内如果超过10个key被修改,则发起快照保存
save 300 10 

#10000秒内如果超过60个key被修改,则发起快照保存
save 60 10000

SAVE命令并不常用,使用SAVE命令在快照创建完毕之 前,redis处于阻塞状态,无法对外服务

2.1.2、服务端触发机制

如果用户在redis.conf中设置了save配置选项,redis会在save选项 条件满足之后自动触发一次BGSAVE命令,如果设置多个save配置选项,当任意一个save配置选项条件满足,redis也会触发一次BGSAVE命令

#900秒内如果超过1个Key被修改则发起快照保存
save 900 1  

#300秒内如果超过10个key被修改,则发起快照保存
save 300 10 

#10000秒内如果超过60个key被修改,则发起快照保存
save 60 10000

2.2.3、配置生成快照名称和位置

# 1.修改生成快照名称
dbfilename dump.rdb

# 2.修改生成位置
dir ./   # 这个表示redis-cli、redis-server这些命令的同级目录

2.2.4、优点

  • RDB快照文件是一个紧凑压缩的二进制文件,非常使用用于备份,全量复制等场景。开发中可以按照每6小时执行一次bgsave备份,用于容灾备份。

  • Redis加载RDB恢复数据远远快于AOF方式

2.2.5、缺点

  • RDB无法做到实时持久化/秒级持久化(两次存在硬盘有时间差),每次bgsave时都需要fork子进程,频繁执行有时间成本。
  • RDB快照文件不同版本格式不一样,容易引起兼容问题。

2.2、AOF方式

AOF与RDB不一样,它是以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令达到恢复数据的目的。解决了数据持久化的实时性的问题。Redis默认是不开启的,需要使用时,需要配置:

# 开启持久化
appendonly yes

# 修改生成文件名称
appendfilename "appendonly.aof"

# 修改日志同步频率 appendfsync everysec|always|no 指定,推荐everysec
appendfsync everysec

解释一下:就是记录每次的 set、setex等写命令,这样当内存中的数据丢失时,我们将所有的写名字重新执行一遍,这样就恢复了内存的数据。

AOF 有3种文件同步策略:

策略解释
appendfsync always收到命令就立即写到磁盘,效率最慢.但是能保证完全的持久化
appendfsync everysec每秒写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
appendfsync no完全以依赖os,一般同步周期是30秒

2.2.1、优点

  • AOF方式数据安全性更高,配置得当,最多损失1秒的数据量

  • 在不小心执行flushall命令,也可以通过AOF方式恢复(删除最后一个命令即可)

  • AOF 日志是一个增量日志文件,不会存在断电时出现损坏问题。即使出现问题,redis-check-aof 工具也能够轻松修复它。

  • 当 AOF 变得太大时,Redis 能够在后台自动重写 AOF

2.2.2、缺点

  • 相同数据量来说,AOF文件体积通常大于RDB文件
  • 数据持久化性能上来说,AOF 比 RDB 慢

2.3、RDB-AOF混合方式

混合持久化是结合了 RDB 和 AOF 的优点,在写入的时候,先把当前的数据以 RDB 的形式写入文件的开头,再将后续的操作命令以 AOF 的格式存入文件。即以 RDB 作为全量备份,AOF 作为增量备份,来提高备份效率。这样既能保证 Redis 重启时的速度,又能防止数据丢失的风险, 这就是 Redis 4.0 之后推出的 RDB-AOF 混合持久化模式,其作为默认配置来使用

2.4、持久化机制的选择

  • 如果对数据安全性有非常高的要求,建议 RDB 和 AOF 同时启用。
  • 如果对数据安全性要求不是很高,能够容忍数据的丢失,建议单独使用 RDB。
  • 不推荐单独使用 AOF,因为对于进行数据库备份、更快重启以及 AOF 引擎中出现错误的情况来说,RDB 是更好的选择。
  • 如果没特殊要求,Redis又是4.x版本以上,可以选择RDB-AOF混合方式。

3、Java操作Redis

  1. 环境依赖

Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐使用 Jedis 连接池代替 Jedis 的直连方式。

4、SpringBoot整合Redis

Spring Boot Data(数据)Redis中提供了RedisTemplateStringRedisTemplate,其中StringRedisTemplateRedisTemplate的子类,两个方法基本一致,不同之处主要体现在操作的数据类型不同

  • RedisTemplate中的两个泛型都是Object,意味着存储的key和value都可以是一个对象
  • StringRedisTemplate的两个泛型都是String,意味着StringRedisTemplate的key和value都只能是字符串。

注意: 使用RedisTemplate默认是将对象序列化到Redis中,所以放入的对象必须实现对象序列化接口 serilizable

3.1、环境准备

  1. 引入依赖
<!--redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redis连接池-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
  1. 配置application.yaml
spring:
  redis:
    host: 192.168.126.132
    port: 6379
    password: 1234
    lettuce:
      pool:
        max-active: 8 # 最大连接
        max-idle: 8   # 最大空闲连接
        min-idle: 0   # 最小空闲连接
        max-wait: 100ms # 连接等待时间
  1. 写完上述配置,我们启动 SpringBoot 时,会给我们自动创建 RedisTemplateStringRedisTemplate,我们只需要注入即可
@SpringBootTest
class KuangStudyRedisApplicationTests {


    //注入StringRedisTemplate
    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    // 测试存字符串String
    @Test
    void testSaveString() {
        // 写入一条String数据
        stringRedisTemplate.opsForValue().set("name","虎哥");
        // 获取String数据
        Object name = stringRedisTemplate.opsForValue().get("name");
        System.out.println("name = " + name);

    }

}

3.2、SpringBoot操作key

  • stringRedisTemplate.delete("key") : 删除一个key
  • stringRedisTemplate.hasKey("key") : 是否有 name 这个key
  • stringRedisTemplate.type("key") : 查看 key 的类型
  • stringRedisTemplate.keys("*") : 查看所有的 key
@SpringBootTest
class KuangStudyRedisApplicationTests {


    //注入StringRedisTemplate
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 测试操作redis中的key
    @Test
    void testKey(){
        // 删除一个key
        // stringRedisTemplate.delete("name");
        // 判断某个key是否存在
        Boolean hasKey = stringRedisTemplate.hasKey("name");
        System.out.println(hasKey);
        
        // 判断key所对应value的类型
        DataType type = stringRedisTemplate.type("name");
        System.out.println(type);
        
        // 获取所有的key
        Set<String> keys = stringRedisTemplate.keys("*");
        keys.forEach(key -> System.out.println("key = " + key));
        
	// 测试key的超时时间 -1表示永不超时, -2 表示 key 不存在
        Long expire = stringRedisTemplate.getExpire("name");
        System.out.println(expire);
        
        // 随机获取一个key
        String s = stringRedisTemplate.randomKey();
        System.out.println(s);
        
        //修改key的名字(判断key是否存在,存在才会修改)
        stringRedisTemplate.renameIfAbsent("name","name1");
        
        // 移动key到指定库
        stringRedisTemplate.move("name",1);
    }

}

3.3、SpringBoot操作String

@SpringBootTest
class KuangStudyRedisApplicationTests {


    //注入StringRedisTemplate
    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    // 测试操作字符串String
    @Test
    void testSaveString() {
        // 写入一条String数据
        stringRedisTemplate.opsForValue().set("name","虎哥");
        // 获取String数据
        Object name = stringRedisTemplate.opsForValue().get("name");
        System.out.println("name = " + name);
        // 设置key的超时时间(设置一个code的key,值value是2357,超时时间是120,单位是TimeUnit.SECONDS 为秒)
        stringRedisTemplate.opsForValue().set("code","2357",120, TimeUnit.SECONDS);
        // 给key对应的value追加值
        stringRedisTemplate.opsForValue().append("name","他是一个好人,单纯少年!");
    }


}

3.4、SpringBoot操作List

  • stringRedisTemplate.leftPush("key","xxx") : 创建一个列表 并放入一个元素
  • stringRedisTemplate.leftPushAll("key","xxx,xxx,xxx") : 创建一个列表 并放入多个元素
  • stringRedisTemplate.opsForList().range("names", 0, -1) : 获取列表,返回一个List集合
  • stringRedisTemplate.opsForList().index("List", "index") : 获取列表中指定下标所对应的值
  • stringRedisTemplate.opsForList().leftPop("List") : 从列表左边弹出一个元素
@SpringBootTest
class KuangStudyRedisApplicationTests {


    //注入StringRedisTemplate
    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    //操作redis中list类型   opsForList 实际操作就是redis中list类型
    @Test
    public void testList(){
        //stringRedisTemplate.opsForList().leftPush("names","小陈");//创建一个列表  并放入一个元素
        //stringRedisTemplate.opsForList().leftPushAll("names","小陈","小张","小王");//创建一个列表 放入多个元素

        List<String> names = new ArrayList<>();
        names.add("xiaoming");
        names.add("xiaosan");
        //stringRedisTemplate.opsForList().leftPushAll("names",names);//创建一个列表 放入多个元素

        // 查看列表
        List<String> stringList = stringRedisTemplate.opsForList().range("names", 0, -1); 
        stringList.forEach(value-> System.out.println("value = " + value));
    }


}

3.5、SpringBoot操作Set

@SpringBootTest
class KuangStudyRedisApplicationTests {


    //注入StringRedisTemplate
    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    //操作redis中set类型   opsForSet 实际操作就是redis中set类型
    @Test
    public void testSet(){
        //创建set 并放入多个元素
        stringRedisTemplate.opsForSet().add("sets","张三","张三","小陈","xiaoming");
        //查看set中成员
        Set<String> sets = stringRedisTemplate.opsForSet().members("sets");
        sets.forEach(value-> System.out.println("value = " + value));

        //获取set集合元素个数
        Long size = stringRedisTemplate.opsForSet().size("sets");
        System.out.println("size = " + size);
    }


}

3.6、SpringBoot操作ZSet

@SpringBootTest
class KuangStudyRedisApplicationTests {


    //注入StringRedisTemplate
    @Autowired
    private StringRedisTemplate stringRedisTemplate;


     @Test
    public void testZset(){
        //创建ZSet并放入元素(小黑的分数是20)
        stringRedisTemplate.opsForZSet().add("zsets","小黑",20);

        //指定范围查询
        Set<String> zsets = stringRedisTemplate.opsForZSet().range("zsets", 0, -1);
        zsets.forEach(value-> System.out.println(value));
        
        System.out.println("=====================================");
        
        //获取指定元素以及分数(分数在0-1000之间)
        Set<ZSetOperations.TypedTuple<String>> zsets1 = stringRedisTemplate.opsForZSet().rangeByScoreWithScores("zsets", 0, 1000);

        zsets1.forEach(typedTuple ->{
            System.out.println(typedTuple.getValue());
            System.out.println(typedTuple.getScore());
        });

    }


}

3.7、SpringBoot操作Hash

@SpringBootTest
class KuangStudyRedisApplicationTests {


    //注入StringRedisTemplate
    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    //操作redis中Hash类型   opsForHash 实际操作就是redis中Hash类型
    @Test
    public void testHash(){

        //创建一个hash类型 并放入key value
        stringRedisTemplate.opsForHash().put("maps","name","张三");

        Map<String,String> map =  new HashMap<String,String>();
        map.put("age","12");
        map.put("bir","2012-12-12");
        //放入多个key value
        stringRedisTemplate.opsForHash().putAll("maps",map);


        //获取多个key的value
        List<Object> values = stringRedisTemplate.opsForHash().multiGet("maps", Arrays.asList("name", "age"));
        values.forEach(value-> System.out.println(value));

        //获取hash中某个key的值
        String value  = (String) stringRedisTemplate.opsForHash().get("maps", "name");

        //获取所有values
        List<Object> vals = stringRedisTemplate.opsForHash().values("maps");

        //获取所有keys
        Set<Object> keys = stringRedisTemplate.opsForHash().keys("maps");


    }


}

3.8、SpringBoot操作对象

SpringBoot操作对象是采用RedisTemplate,RedisTemplate默认是将对象序列化到 Redis 中,所以放入的对象必须实现对象序列化接口。

在存对象时,我们会让key采用StringRedisSerializer方式,而value采用默认的JdkSerializationRedisSerializer序列化方式。

  • redisTemplate.setKeySerializer(new StringRedisSerializer()) : 修改key的序列化,使得存key的序列化采用StringRedisSerializer方式
  • redisTemplate.setHashKeySerializer(new StringRedisSerializer()) : 在我们操作Hash类型时,也要加上这一句,使得修改hash key 的序列化(因为Hash有两个key
@SpringBootTest
public class KuangStudyRedisTemplateTests {

    // 注入RedisTemplate
    @Autowired
    private RedisTemplate redisTemplate;

    void testRedisTemplate(){
        // 修改key的序列化,使得存key的序列化采用StringRedisSerializer方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 修改hash key 的序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

        // 创建一个User对象
        User user = new User();
        user.setId(UUID.randomUUID().toString());
        user.setName("林晓");
        user.setAge(23);
        user.setBir(new Date());
        // 将对象序列化后存入redis
        redisTemplate.opsForValue().set("user",user);
        // 获取对象需要进行反序列化
        User user1 = (User) redisTemplate.opsForValue().get("user");
        System.out.println(user1);


        // 创建一个列表并放入user对象
        redisTemplate.opsForList().leftPush("list",user);
        // 创建一个Set并放入user对象
        redisTemplate.opsForSet().add("set",user);
        // 创建一个ZSet并放入user对象
        redisTemplate.opsForZSet().add("zset",user,10);
        // 创建一个Hash并放入user对象(注意需要序列化两个key)
        redisTemplate.opsForHash().put("map","name",user);

    }
}

3.9、BoundAPI

BoundAPI可以绑定某个key,后续的所有操作都是针对这个key进行操作的。

@SpringBootTest
public class KuangStudyBoundAPITests {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    // spring data 为了方便我们对redis进行更友好的操作 因此有提供了bound api 简化操作
    @Test
    public void testBound(){

        // 对key序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 对Hash key 序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());


        //对字符串类型key进行绑定 后续所有操作都是基于这个key的操作
        BoundValueOperations<String, String> nameValueOperations = stringRedisTemplate.boundValueOps("name");
        // 对name设置value为张三
        nameValueOperations.set("张三");
        // 对name的value追加
        nameValueOperations.append("是一个好人");
        // 获取name的value
        String s1 = nameValueOperations.get();
        System.out.println(s1);

        //对list set zset hash
        //对list类型key进行绑定 后续所有操作都是基于这个key的操作
        BoundListOperations<String, String> listsOperations = stringRedisTemplate.boundListOps("lists");
        // 对lists设置value
        listsOperations.leftPushAll("张三","李四","小陈");
        // 获取lists
        List<String> lists = listsOperations.range(0, -1);
        lists.forEach(list-> System.out.println(list));

      
        //set
        //redisTemplate.boundSetOps();
        //stringRedisTemplate.boundSetOps()
        
        //zset
        //stringRedisTemplate.boundZSetOps();
        //redisTemplate.boundZSetOps();
        
        //hash
        //stringRedisTemplate.boundHashOps();
        //redisTemplate.boundHashOps()

}

3.10、总结

  1. 针对于日后处理key value 都是 String 使用 StringRedisTemplate
  2. 针对于日后处理的key value 存在对象 使用 RedisTemplate
  3. 针对于同一个key多次操作可以使用boundXXXOps()
    • XXX包含: Value List Set Zset Hash的 api 简化书写

5、Redis的应用场景

  1. 利用redis 中字符串类型完成项目中手机验证码存储的实现
  2. 利用redis 中字符串类型完成具有失效性业务功能。 例如12306,淘宝的订单还有:40分钟
  3. 利用redis 分布式集群系统中: Session共享
  4. 利用redis zset类型:可排序set类型,元素,分数,排行榜之类功能
  5. 利用redis 实现分布式缓存
  6. 利用redis 存储认证之后token信息
  7. 利用redis 解决分布式集群系统中分布式锁问题

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

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

相关文章

黑马JavaWeb课程中安装vue脚手架出现的问题

1 安装node.js 要想前端工程化&#xff0c;必须安装node.js&#xff0c;前端工程化的环境。 在成功安装node.js后&#xff0c; 修改全局包安装路径为Node.js安装目录&#xff0c; 修改npm镜像源为淘宝镜像源&#xff0c;这里出现第一个问题&#xff0c;视频中给的淘宝镜像为&…

element-plus表格合并

要实现这样的表格&#xff0c; 怎么做呢&#xff1f; 甚至是这种三级的呢&#xff1f; 官网的案例也是通过这个方法进行配置的&#xff0c;也就是说表格长什么样&#xff0c;关键在怎么处理的方法上。 这是官网的方法&#xff0c;可参考拓展&#xff1a; const arraySpanMeth…

sqllibs-第46关

从本关开始&#xff0c;我们开始学习order by 相关注入的知识。 尝试?sort1 desc或者asc&#xff0c;显示结果不同&#xff0c;则表明可以注入。&#xff08;升序or降序排列 sort1时是第一列Id sort2时是第二列 username sort3时是第三列 password 当sort4时报错 相当于使用or…

Redis 之六:Redis 的哨兵模式(Sentinel)

Redis 哨兵&#xff08;Sentinel&#xff09;模式是一种高可用性解决方案&#xff0c;用于监控和自动故障转移的集群系统。 在 Redis Sentinel 架构中&#xff0c;哨兵是一组运行在特殊模式下的 Redis 进程&#xff0c;它们可以监控一个或多个主从复制结构中的 Redis 主服务器以…

Java中封装的解析

在 Java 中&#xff0c;封装是一种将类的内部实现细节隐藏起来&#xff0c;只向外部暴露必要的接口的机制。这样可以提高代码的安全性、可维护性和可重用性。 封装的主要目的是保护类的内部状态不被外部直接访问&#xff0c;同时提供公共的方法来操作这些状态。通过封装&#…

什么是VR虚拟现实|虚拟科技博物馆|VR设备购买

虚拟现实&#xff08;Virtual Reality&#xff0c;简称VR&#xff09;是一种通过计算机技术模拟出的一种全新的人机交互方式。它可以通过专门的设备&#xff08;如头戴式显示器&#xff09;将用户带入一个计算机生成的虚拟环境之中&#xff0c;使用户能够与这个虚拟环境进行交互…

什么是VR科学教育|VR数字文化遗产展示|VR设备

VR科学教育是指利用虚拟现实&#xff08;Virtual Reality&#xff0c;简称VR&#xff09;技术在科学教育领域中进行教学和学习的方法。通过VR技术&#xff0c;学生可以沉浸在虚拟环境中&#xff0c;与三维模型互动&#xff0c;观察和探索科学现象&#xff0c;从而更加直观地理解…

亚信安慧AntDB:融合架构下的数据管理利器

AntDB的独特架构将集中式和分布式部署模式巧妙融合&#xff0c;为用户提供了全方位的数据管理解决方案。这种一站式的特性使得用户无需在不同系统间来回切换&#xff0c;极大地提高了工作效率。 AntDB同时具备集中式和分布式系统的优点&#xff0c;集中式架构拥有简单易用、管…

git遇到的报错

这是小编在gits所遇到的报错集合&#xff0c;可能不会包含大家的报错信息但是咱们可以在评论区留言共同收集git的报错信息以及解决方法&#xff01; SSL证书问题 解决办法&#xff1a;git命令下运行 git config --global http.sslVerify false 然后再进行重新clone代码 Git应…

css实现居中

基础代码&#xff1a; <div class"box"><div class"content"></div> </div> css实现居中的几种方式&#xff1a; 1、flex布局&#xff08;水平垂直&#xff09; .box {width: 200px;height: 200px;background-color: pink;disp…

通过SMI(MDC/MDIO)读取外部PHY寄存器

一、基础介绍&#xff1a; SMI&#xff1a;串行管理接口&#xff08;Serial Management Interface&#xff09;&#xff0c;也被称作MII管理接口&#xff08;MII Management Interface&#xff09;&#xff0c;包括MDC和MDIO两条信号线。 MDIO是一个PHY的管理接口&#xff0c…

babylonjs入门-方向光

基于babylonjs封装的一些功能和插件 &#xff0c;希望有更多的小伙伴一起玩babylonjs&#xff1b; 欢迎加群&#xff08;点击群号传送&#xff09;&#xff1a;464146715 官方文档 中文文档 案例传送门 懒得打字 粘贴复制 一气呵成 ​

JavaSE——常用API(2/3)-String使用时的注意事项、String的应用案例

目录 String使用时的注意事项 阅读程序并解答&#xff08;1&#xff09; 阅读程序并解答&#xff08;2&#xff09; String的应用案例 案例1 案例2 小结 String使用时的注意事项 String对象的内容不可改变&#xff0c;被称为不可变字符串对象。&#xff08;每次试图改变…

多输入多输出 | Matlab实现RIME-BP霜冰算法优化BP神经网络多输入多输出预测

多输入多输出 | Matlab实现RIME-BP霜冰算法优化BP神经网络多输入多输出预测 目录 多输入多输出 | Matlab实现RIME-BP霜冰算法优化BP神经网络多输入多输出预测预测效果基本介绍程序设计往期精彩参考资料 预测效果 基本介绍 多输入多输出 | Matlab实现RIME-BP霜冰算法优化BP神经网…

一起玩儿平衡车(ESP32)——02 平衡车的组装与接线方法

摘要&#xff1a;本文介绍平衡车的组装与接线方法 前边介绍了所要实现的平衡车的组成&#xff0c;接下来就来把小车组装起来。首先是下层底板的底面要固定两个轮子。这个只要固定孔位没有问题&#xff0c;用螺丝直接将轮子支架固定上去就可以了。固定好后如下图所示&#xff1…

nginx(三)重写功能 防盗链 方向代理 等

return 可以写在location server if 里面 return用于完成对请求的处理&#xff0c;并直接向客户端返回响应状态码&#xff0c;比如:可以指定重定向URL(对于特殊重定向状态码&#xff0c;301/302等) 或者是指定提示文本内容(对于特殊状态码403/500等)&#xff0c;处于此指令后…

怎么压缩成mp4视频?

在数字化时代&#xff0c;视频已经成为我们日常生活中不可或缺的一部分。然而&#xff0c;有时候我们可能会遇到视频文件太大的问题&#xff0c;不便于传输、存储或分享。那么&#xff0c;如何将视频压缩成MP4格式&#xff0c;以减小文件大小呢&#xff1f;本文将为您介绍几种简…

JavaWeb HTTP 请求头、请求体、响应头、响应体、响应状态码

J2EE&#xff08;Java 2 Platform Enterprise Edition&#xff09;是指“Java 2企业版”&#xff0c;B/S模式开发Web应用就是J2EE最核心的功能。 Web是全球广域网&#xff0c;也称为万维网(www)&#xff0c;能够通过浏览器访问的网站。 在日常的生活中&#xff0c;经常会使用…

Doccano 修复 spacy.gold 的bug

引言 最初只是想把Doccano标注的数据集转换成BIO(类似conll2003数据集)的标注格式&#xff1b; 按照PR的修改意见实现了修改&#xff0c;但是本人不建议这么做&#xff1b; 应该随着Doccano的升级&#xff0c;Doccano的导出格式发生了变化&#xff0c;而原来的doccano-transfo…

高效备考2024年AMC10:吃透2000-2023年1250道AMC10真题

距离2024年AMC10的比赛只有8个月多一点的时间了&#xff0c;如何备考AMC10美国数学竞赛最有效&#xff1f;参加AMC10竞赛是否一定要参加机构的培训班&#xff1f;吃透历年真题是有效的自学、了解AMC10和备考策略之一。事实上&#xff0c;网络上有很多关于AMC10的学习资源&#…