Spring Boot 项目中 Redis 与数据库性能对比实战:从缓存配置到时间分析,详解最佳实践

news2024/11/24 11:20:31

一、前言:

    在现代应用中,随着数据量的增大和访问频率的提高,如何提高数据存取的性能变得尤为重要。缓存技术作为一种常见的优化手段,被广泛应用于减少数据库访问压力、提升系统响应速度。Redis 作为一种高效的内存缓存数据库,因其卓越的性能和丰富的数据类型支持,在开发中占据了重要位置。

    本篇文章将详细介绍如何在 Spring Boot 项目中使用 Redis,创建简单的缓存机制,并实现数据存取的时间比较。通过这个实战项目,你将学习如何在 Redis 和 MySQL 之间存取数据,并测量两者的性能差异,从而对缓存策略有更加深入的理解。

二、详细操作:

2.1、环境准备和项目结构

首先,你需要准备一个 Spring Boot 项目,并确保项目结构中包含以下依赖:

  • Spring Web:用于构建 RESTful API
  • MyBatis:数据库操作框架
  • Spring Data Redis:用于 Redis 操作
  • MySQL Driver:用于与 MySQL 数据库交互
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- MyBatis Starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>
        <!-- Spring Boot Redis Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>
    </dependencies>

同时项目结构如下,是一个非常普通的SpringBoot项目

同时还要在application.properties中配置好对应的MySQL和MyBatis信息:

spring.datasource.url=jdbc:mysql://localhost:3306/yourDatabase
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis.mapper-locations=classpath:mapper/*.xml

2.2、创建实体类

实体类 User 用于表示用户的基本信息,包含 idname 两个字段。代码如下:

@Data
public class User {
    private Long id;
    private String name;
}

2.3、配置MyBatis Mapper层

创建一个接口 UserMapper 来操作数据库,并通过 MyBatis 的 XML 文件实现具体的 SQL 操作:

@Mapper
public interface UserMapper {
    User selectUserById(Long id);
}
<mapper namespace="com.example.redis.mapper.UserMapper">
    <select id="selectUserById" resultType="com.example.redis.entity.User">
        SELECT id, name FROM user WHERE id = #{id}
    </select>
</mapper>

2.4、Redis配置与工具类

自定义的 Redis 配置类 RedisConfig,它基于 Jackson 的 JSON 序列化器进行 Redis 数据存储与读取,确保数据可以以对象形式保存到 Redis 中。(这个配置类和工具类是在网上找的)配置如下:

@Configuration
public class RedisConfig {
    // 自己定义了一个RedisTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        // 我们为了自己开发方便,一般直接使用 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        // Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

工具类 RedisUtil 提供了简单的 Redis 操作方法,例如设置键值对、获取缓存、检查键是否存在等:

package com.example.redis.utils;

import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public final class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    public Set<String> keys(String keys){
        try {
            return redisTemplate.keys(keys);
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }
    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
     /**
     * 普通缓存放入, 不存在放入,存在返回
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean setnx(String key, Object value) {
        try {
            redisTemplate.opsForValue().setIfAbsent(key,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

  /**
     * 普通缓存放入并设置时间,不存在放入,存在返回
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean setnx(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }
    /**
     * 递减
     * @param key 键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
    /**
     * HashGet
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }
    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }
    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }
    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param key 键
     * @param item 项
     * @param by 要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }
    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param by 要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }
    /**
     * 根据key获取Set中的所有值
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 根据value从一个set中查询,是否存在
     * @param key 键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 将set数据放入缓存
     * @param key 键
     * @param time 时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0){
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 获取set缓存的长度
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 移除值为value的
     * @param key 键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    // ===============================list=================================
    /**
     * 获取list缓存的内容
     * @param key 键
     * @param start 开始
     * @param end 结束 0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 获取list缓存的长度
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 通过索引 获取list中的值
     * @param key 键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     *
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 移除N个值为value
     * @param key 键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
}

2.5、Service层实现

UserService 中,创建数据时不仅将数据写入数据库,还将其缓存到 Redis 中。查询数据时,优先从 Redis 中获取,若 Redis 中不存在,则查询数据库并缓存结果。还会记录并输出 Redis 和数据库的查询时间:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisUtil redisUtil;

    public User  getUserById(Long id){
        //先从Redis中读取数据
        String key = "user:"+id;
        //判断该数据是否在Redis中
        long redisStartTime = System.currentTimeMillis();
        Object testUser = redisUtil.hasKey(key)?redisUtil.get(key):null;//先判断是否有,有就得到,没有就返回null
        long redisEndTime = System.currentTimeMillis();
        //存在的话则返回数据
        if(testUser!=null){
            System.out.println("Redis 查询时间: " + (redisEndTime - redisStartTime) + " ms");
            return (User) testUser;
        }
        //不存在的话则从数据库中读取数据并将数据保存到Redis中
        long dbStartTime = System.currentTimeMillis();
        User user = userMapper.selectUserById(id);
        long dbEndTime = System.currentTimeMillis();
        System.out.println("数据库查询时间: " + (dbEndTime - dbStartTime) + " ms");

        redisUtil.set(key,user);
        return user;
    }

}

2.6、Controller层实现

通过 UserController 提供 RESTful 接口,用于创建用户和查询用户信息:

@RestController
@RequestMapping("/test")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User test1(@PathVariable Long id) {
        return userService.getUserById(id);
    }
}

三、Redis相关知识点整理:

1. Redis 在缓存中的作用

在本项目中,Redis 主要用于缓存用户数据。通常,在频繁的数据库读操作中,每次查询数据库都会耗费一定的时间和资源。而 Redis 作为一个基于内存的缓存,可以极大地减少对数据库的直接访问,提升系统性能。

在下面我展示一下使用数据库与Redis查询数据的时间对比,还是很明显的,使用Redis能很大程度地减少查询时间

2. Redis 与数据库的数据存取逻辑

UserService 中,我们实现了从 Redis 获取数据的逻辑:

Object cachedUser = redisUtil.hasKey(key) ? redisUtil.get(key) : null;

这里,我们首先判断 Redis 中是否已经缓存了对应用户数据(通过 redisUtil.hasKey(key))。如果缓存存在,则直接从 Redis 中获取数据,从而避免了数据库查询的延时。如果缓存不存在,则执行数据库查询,并将查询结果存入 Redis:

User user = userMapper.selectUserById(id);
redisUtil.set(key, user);

这种 "缓存穿透" 的模式,确保了只有在 Redis 缓存未命中的情况下才会访问数据库,从而实现了高效的数据查询。

3. Redis 数据的存储与序列化

在 Redis 中存储复杂数据类型(如 Java 对象)时,使用了 Jackson2JsonRedisSerializer 来序列化对象:

Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);

这段代码确保了 Redis 可以将 User 对象序列化为 JSON 字符串并存储在 Redis 中,当需要从缓存中获取数据时,再反序列化为 Java 对象。

四、总结:

    在本篇文章中,我们通过一个简单的 Spring Boot 项目,结合 Redis 和 MySQL 实现了数据的存取,并比较了它们在时间上的差异。通过 Redis 的引入,可以显著提升系统的性能,特别是在频繁读写的场景下,缓存策略能够有效减轻数据库的压力。

    本项目展示了如何使用 Redis 来优化应用性能,以及 Redis 在现代应用架构中的重要性。如果你正在构建一个需要高性能、低延迟的应用,Redis 绝对是你不可或缺的技术之一。

    如果这篇文章有帮助到你的话,就点个赞和关注吧,你的鼓励是我最大的动力!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DBeaver连接mysql 9报错:Public Key Retrieval is not allowed

DBeaver连接mysql 9报错&#xff1a;Public Key Retrieval is not allowed 如图&#xff1a; 解决方案 编辑连接属性&#xff1a; 修改 allowPublicKeyRetrieval 的值为 true DBeaver连接mysql数据库执行.sql脚本&#xff0c;Windows_dbeaver执行sql脚本.sql文件-CSDN博客文章…

【Java】 —— 数据结构与集合源码:Vector、LinkedList在JDK8中的源码剖析

目录 7.2.4 Vector部分源码分析 7.3 链表LinkedList 7.3.1 链表与动态数组的区别 7.3.2 LinkedList源码分析 启示与开发建议 7.2.4 Vector部分源码分析 jdk1.8.0_271中&#xff1a; //属性 protected Object[] elementData; protected int elementCount;//构造器 public …

2024ccna考试时间?新手小白看这些就够了

2024年想要考取ccna证书的新手小白们&#xff0c;是不是正在为考试时间而烦恼呀&#xff0c;其实ccna的考试时间其实非常灵活&#xff0c;并不需要像其他考试那样死记硬背固定的日期。那么小编马上就给大家说说2024ccna考试时间&#xff0c;并且附带一些考试内容&#xff0c;让…

LeetCode[简单] 70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 思路 利用滚动数组 public class Solution {public int ClimbStairs(int n) { //滚动数组int f0 0, f1 0, f2 1;for(int i 1; i < n; i){…

拱坝与重力坝:结构特性与应用差异的深度解析

在水利工程领域&#xff0c;坝体结构的选择对于工程的稳定性、安全性以及经济效益具有至关重要的影响。拱坝与重力坝作为两种主要的坝型&#xff0c;各自具有独特的结构特性和应用场景。本文旨在深入探讨拱坝与重力坝的区别&#xff0c;从工程特点、坝体结构型式、材料选用、水…

国内如何下载谷歌浏览器(chrome浏览器)历史版本和chromedriver驱动,长期更新,建议收藏

众所周知&#xff0c;google是一直被国内屏蔽的&#xff0c;有时候想要下载个chrome浏览器都要去外网&#xff0c;或者到处去搜索才能下载到。因为下载chrome浏览器的这个网址&#xff1a;google.com/chrome/ 在国内是一直被屏蔽掉的。 今天主要讲解的是国内ChromeDriver 的下…

ET实现游戏中的红点提示系统(服务端)

目录 ☝&#x1f913;前言 ☝&#x1f913;一、实现思路 ☝&#x1f913;二、实现 &#x1f920;2.1 定义红点组件 &#x1f920;2.2 定义Proto消息体 &#x1f920;2.3 RedPointComponentSystem ☝&#x1f913;难点 ☝&#x1f913;前言 当我们闲来无事时打开农药想消…

数据结构-5.3.二叉树的定义和基本术语

一.二叉树的基本概念&#xff1a; 树是一种递归定义的数据结构&#xff0c;因此二叉树是递归定义的数据结构。 二.二叉树的五种状态&#xff1a; 三.几个特殊的二叉树&#xff1a; 1.满二叉树&#xff1a;结点总数就是通过等比数列公式求出来的&#xff0c;首项为1即根节点&a…

【网络协议】TCP协议常用机制——延迟应答、捎带应答、面向字节流、异常处理,保姆级详解,建议收藏

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;计算机网络那些事 前几篇文章&#xff0c;博主带大家梳理了一下TCP协议的几个核心机制&#xff0c;比如保证可靠性的 确认应答、超时重传 机制&#xff0c;和提高传输效率的 滑动窗口及其相关优化机…

C++ Builder XE12关于KonopkaControls与TMS VCL UI Pack组件的安装

1、先打开open project&#xff0c;选中安装的组件工程&#xff0c;并打开。 2、在option中设置 3、点击编译并进行安装install

洞察AI趋势:智享AI直播,打造专属你的数字化直播AIGC系统!

洞察AI趋势&#xff1a;智享AI直播&#xff0c;打造专属你的数字化直播AIGC系统&#xff01; 在当今这个日新月异的数字时代&#xff0c;人工智能&#xff08;AI&#xff09;已不再是遥不可及的未来科技&#xff0c;而是正深刻改变着我们生活、工作的每一个角落。其中&#xf…

幽默视频下载网站推荐

在快节奏的生活中&#xff0c;搞笑视频无疑是缓解压力的良药&#xff0c;不论是自制搞笑视频还是寻找素材来增添创作的趣味性&#xff0c;找到合适的视频素材至关重要。幸运的是&#xff0c;网络上有许多优秀的网站能够满足这一需求。以下是8个适合下载幽默搞笑视频素材的网站&…

华为 静态路由和bfd 侦测的实验

实验要求 sw1 上业务地址192.168.1.1/24 SW3 业务地址192.168.2.1/24 正常情况下走主链路&#xff0c;不正常的情况下走备份链路 2 配置 这是基本地址配置 开启了bfd 本端地址为 10.1.1.1 对端地址是10.1.1.2 关键是discrimination 分辨参数 …

静态路由和nqa 联动实验

nqa 配置 1 test 断端口 很明显是切换到备机上了

Word中如何删除表格下一页的空白页

Reference&#xff1a; [1] Word空白页怎么都删除不掉&#xff1f;用这6个方法随便删&#xff01; - 知乎 (zhihu.com)

前端埋点学习

前端埋点 前端数据埋点是在前端页面中通过代码的方式手机用户行为数据和页面性能的过程&#xff0c;通过在页面中插入指定的代码&#xff0c;实现实时监控用户在页面上的操作行为。 通常包括一下事件 定义事件: 定义需要手机的数据事件&#xff0c;如点击&#xff0c;浏览等添…

基于Docker的FRP内网穿透部署

服务器搭建&#xff08;FRPS&#xff09; 创建配置文件 # 创建存放目录 sudo mkdir /etc/frp # 创建frps.ini文件 nano /etc/frp/frps.ini frps.ini内容如下&#xff1a; [common] # 监听端口 bind_port 7000 # 面板端口 dashboard_port 7500 # 登录面板账号设置 dashboa…

【进阶OpenCV】 (9)--摄像头操作--->答题卡识别改分项目

文章目录 项目&#xff1a;答题卡识别改分1. 图片预处理2. 描绘轮廓3. 轮廓近似4. 透视变换5. 阈值处理6. 找每一个圆圈轮廓7. 将每一个圆圈轮廓排序8. 找寻所填答案&#xff0c;比对正确答案8.1 思路8.2 图解8.3 代码体现 9. 计算正确率 总结 项目&#xff1a;答题卡识别改分 …

数论与同余 - 离散数学系列(七)

目录 1. 整数的性质 整除与因数 最大公约数与最小公倍数 2. 欧几里得算法 算法步骤 3. 模运算与同余 模运算 同余关系 同余的性质 4. 数论在密码学中的应用 RSA 加密算法 5. 实际应用场景 1. 数字签名 2. 哈希函数与数据完整性 3. 密钥交换 6. 例题与练习 例题…

Java:方法详解

目录 一.什么是方法(method) 二.方法定义 三.方法中实参和形参的关系 四.方法重载 五.递归 一.什么是方法(method) 方法就是一个代码片段&#xff0c;再C语言中我们曾经学过一个类似的方式——函数&#xff0c;他们都是将具有独立功能的代码组织成一个整体&#xff0c;形成…