JAVA开发(Redis的主从与集群)

news2024/11/28 22:55:08

现在web项目无处不在使用缓存技术,redis的身影可谓无处不在。但是又有多少项目使用到的是redis的集群?大概很多项目只是用到单机版的redis吧。作为缓存的一块,set ,get数据。用的不亦乐乎。但是对于高可用系统来说,数据集群是很有必要的。

我们看单机版的redis配置。

springBoot引入maven依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

配置springboot  yml文件 redis连接

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    maxIdle: 20
    minIdle: 10
    maxTotal: 100
    database: 2
    busiDb: 9
    boeDb: 2
    eximportDb: 5
    session:
      store-type: redis
  cache:
    type: redis

操作redis的工具类:

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import com.google.gson.Gson;

import cn.ctg.common.enums.EnumType;
import cn.ctg.common.util.constants.Constant;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;

/**
 * Redis工具类
 *
 */
@Component
public class RedisUtils {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private RedisTemplate redisTemplate;
    
    @Resource(name = "redisTemplate")
    private ValueOperations<String, String> valueOperations;
 
    @Autowired
    private RedisExtendService redisExtendService;

    /** 加分布式锁的LUA脚本 */
    private static final String LOCK_LUA =
        "if redis.call('setNx',KEYS[1],ARGV[1])==1  then return redis.call('expire',KEYS[1],ARGV[2])  else  return 0 end";

    /** 计数器的LUA脚本 */
    private static final String INCR_LUA =
        "local current = redis.call('incr',KEYS[1]);" +
            " local t = redis.call('ttl',KEYS[1]); " +
            "if t == -1 then  " +
            "redis.call('expire',KEYS[1],ARGV[1]) " +
            "end; " +
            "return current";

    /** 解锁的LUA脚本 */
    private static final String UNLOCK_LUA =
        "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    private static final Long SUCCESS = 1L;

    /** 互斥锁过期时间(分钟) */
    private static final long MUTEX_WAIT_MILLISECONDS = 50;
    /** 编号规则生成线程等待次数 ((10 * 1000) / 50) + 1 */
    public static final long RULE_CODE_THREAD_WAIT_COUNT = 200;
    /** 互斥锁等待时间(毫秒) */
    private static final long MUTEX_EXPIRE_MINUTES = 3;
    
    /**
	     * 不设置过期时长
	*/
	public final static long NOT_EXPIRE = -1;
    
	/**
	     * 默认过期时长,单位:秒 
	*/
	public final static long DEFAULT_EXPIRE = 7200; // 2小时
	
    /**
                * 会员卡缓存失效时间  2小时
     */
    public final static long CARD_DEFAULT_EXPIRE = 7200;
    
    /**
               * 默认过期时长,1天
     */
    public final static long DEFAULT_A_DAY = 86400;
    
    /**
               * 默认过期时长,1分钟
     */
    public final static long DEFAULT_A_MIN = 60 ;
    
    /**
            * 默认过期时长,2分钟
	*/
	public final static long DEFAULT_TWO_MIN = 120 ;

    /**
     * 保存数据
     *
     * @param key
     * @param value
     * @param expire 过期时间,单位s
     */
    public void set(String key, Object value, long expire) {
        String valueJson = toJson(value);
        valueOperations.set(key, valueJson);
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        redisExtendService.redisDataChange(key, valueJson, EnumType.CRUD_TYPE.CREATE.getValue());
    }

    /**
     * 判断key是否存在
     *
     * @param key
     */
    public Boolean hasKey(String key) {
        if (StringUtils.isNotBlank(key)) {
            return valueOperations.getOperations().hasKey(key);
        }
        return Boolean.FALSE;
    }

    /**
     * @param key
     * @param value
     */
    public void set(String key, Object value) {
        set(key, value, NOT_EXPIRE);
    }

    public <T> T get(String key, Class<T> clazz, long expire) {
        String value = Convert.toStr(valueOperations.get(key));
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return value == null ? null : fromJson(value, clazz);
    }

    /**
     * 批量从Redis中获取数据
     *
     * @param valueMap 需要存储的数据集合
     * @param expire 过期时间,秒
     * @return java.util.List<T> 返回值
     */
    public void batchSet(Map<String, String> valueMap, long expire) {
        valueOperations.multiSet(valueMap);
        if (expire != NOT_EXPIRE) {
            for (String key : valueMap.keySet()) {
                redisTemplate.expire(key, expire, TimeUnit.SECONDS);
            }
        }
    }

    /**
     * 批量删除
     *
     * @param keys 需要删除的KEY集合
     * @return void
     */
    public void batchDelete(Collection<String> keys) {
        redisTemplate.delete(keys);
    }

    /**
     * 批量从Redis中获取数据
     *
     * @param keyList 需要获取的Key集合
     * @param clazz 需要转换的类型
     * @return java.util.List<T> 返回值
     */
    public <T> Map<String, T> batchGet(List<String> keyList, Class<T> clazz) {
        List<String> objectList = valueOperations.multiGet(keyList);
        Map<String, T> map = new LinkedHashMap<>(objectList.size());
        for (int i = 0; i < keyList.size(); i++) {
            String value = Convert.toStr(objectList.get(i));
            if (!String.class.equals(clazz)) {
                map.put(keyList.get(i), fromJson(value, clazz));
            } else {
                map.put(keyList.get(i), (T)value);
            }
        }
        return map;
    }

    public <T> T get(String key, Class<T> clazz) {
        return get(key, clazz, NOT_EXPIRE);
    }

    /**
     * 使用 父编码+当前编码获取+集团+语言 获取名称
     *
     * @param code 父编码
     * @param dictCode 当前编码
     * @param language 语言 UserUtils.getLanguage()
     * @param groupId 集团ID
     */
    public String get(String code, String dictCode, String language, String groupId) {
        if (StringUtils.isBlank(dictCode)) {
            return "";
        }
        String key = RedisKeys.getSysDictKey(code, dictCode, language, groupId);
        return get(key, NOT_EXPIRE);
    }

    public String get(String key, long expire) {
        String value = Convert.toStr(valueOperations.get(key));
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return value;
    }

    public String get(String key) {
        return get(key, NOT_EXPIRE);
    }

    public void delete(String key) {
        redisTemplate.delete(key);
        redisExtendService.redisDataChange(key, "", EnumType.CRUD_TYPE.DELETE.getValue());
    }

    /**
     * Object转成JSON数据
     */
    private String toJson(Object object) {
        if (object instanceof Integer || object instanceof Long || object instanceof Float || object instanceof Double
            || object instanceof Boolean || object instanceof String) {
            return String.valueOf(object);
        }
        return new Gson().toJson(object);
    }

    /**
     * JSON数据,转成Object
     */
    private <T> T fromJson(String json, Class<T> clazz) {
        return new Gson().fromJson(json, clazz);
    }

    /**
     * 获取分布式锁,默认过期时间3分钟
     *
     * @param key 锁的KEY
     * @return java.lang.Boolean true为获取到锁,可以下一步业务, false为没有获取到锁
     */
    public Boolean setMutexLock(String key) {
        return setMutexLockAndExpire(key, getMutexLockExpireMinutes(), TimeUnit.MINUTES);
    }

    /**
     * 获取分布式锁,带Redis事务
     *
     * @param key 锁的KEY
     * @param timeout 锁时效时间,默认单位:秒
     * @param unit 锁失效时间单位,为null则默认秒
     * @return java.lang.Boolean true为获取到锁,可以下一步业务, false为没有获取到锁
     */
    public Boolean setMutexLockAndExpire(String key, long timeout, TimeUnit unit) {
        return setMutexLockAndExpire(key, Constant.RESULT_1, timeout, unit);
    }

    /**
     * 获取分布式锁,带Redis事务
     * 适用于同一业务,不同的请求用不同的锁,把value当成
     * @param key 锁的KEY
     * @param value 锁的值,一定要跟解锁的值一样,否则会导致无法解锁
     * @param timeout 锁时效时间,默认单位:秒
     * @param unit 锁失效时间单位,为null则默认秒
     * @return java.lang.Boolean true为获取到锁,可以下一步业务, false为没有获取到锁
     */
    public Boolean setMutexLockAndExpire(String key, String value, long timeout, TimeUnit unit) {
        value = StrUtil.appendIfMissing(StrUtil.prependIfMissing(value,"\""),"\"");
        Long result = executeLua(key, value, LOCK_LUA, timeout, unit, Long.class);
        return SUCCESS.equals(result);
    }

    /**
     * 解锁
     *
     * @param key 锁的Key
     * @return boolean
     */
    public boolean unlock(String key) {
        return unlock(key, Constant.RESULT_1);
    }

    /**
     * 解锁
     *
     * @param key 锁的Key
     * @param value 锁的value,一定要跟加锁的value一致,否则会认为不是同一个锁,不会释放
     * @return boolean
     */
    public boolean unlock(String key, String value) {
        value = StrUtil.appendIfMissing(StrUtil.prependIfMissing(value,"\""),"\"");

        Long result = executeLua(key, value, UNLOCK_LUA,null, null, Long.class);

        return SUCCESS.equals(result);
    }

    /**
     * 获取等待锁,如果没有获取到锁就一直等待获取,直到超过waitTime的时间
     *
     * @param key 锁的key
     * @param timeout 锁的超时时间
     * @param unit 锁的超时时间单位
     * @param waitTime 获取锁时的等待时间,一直等不超时则填-1,单位:毫秒
     * @return java.lang.Boolean true为获取到锁,可以下一步业务, false为没有获取到锁
     */
    public Boolean setMutexWaitLock(String key, long timeout, TimeUnit unit, long waitTime) {
        long start = System.currentTimeMillis();
        while (true) {
            boolean result = setMutexLockAndExpire(key, timeout, unit);
            if (result) {
                return true;
            } else {
                long current = System.currentTimeMillis();
                // 超过等待时间还没获取到锁则返回false
                if (waitTime > 0 && (current - start > waitTime)) {
                    logger.warn("redis分布式锁获取失败,key[{}],等待时间[{}]", key, waitTime);
                    return false;
                }
                // 等待100毫秒后重试
                ThreadUtil.sleep(100);
            }
        }
    }

    public long getMutexLockExpireMinutes() {
        return MUTEX_EXPIRE_MINUTES;
    }

    /**
     * 获取自增序列号
     *
     * @param key 序列号的KEY
     * @param seq 自增值,默认自增1
     * @return java.lang.Long 自增后的值
     */
    public Long incr(String key, Long seq, long timeout, TimeUnit unit) {
        return executeLua(key, null, INCR_LUA, timeout, unit, Long.class);
    }

    /**
     * 执行LUA脚本
     *
     * @param key redisKey
     * @param value 值
     * @param lua lua脚本
     * @param timeout 超时时间
     * @param unit 超时单位
     * @param clazz 返回值类型
     * @return T 返回值
     */
    public <T> T executeLua(String key, Object value, String lua, Long timeout, TimeUnit unit, Class<T> clazz){
        // 有时间单位则转成秒,否则默认秒
        if (unit != null) {
            timeout = unit.toSeconds(timeout);
        }
        List<String> args = new ArrayList<>(2);
        if(value != null){
            args.add(Convert.toStr(value));
        }
        if(timeout != null){
            args.add(Convert.toStr(timeout));
        }
        //spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本异常,此处拿到原redis的connection执行脚本
        T result = (T)redisTemplate.execute(new RedisCallback<T>() {
            @Override
            public T doInRedis(RedisConnection connection) throws DataAccessException {
                Object nativeConnection = connection.getNativeConnection();
                // 集群模式和单点模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
                // 集群
                if (nativeConnection instanceof JedisCluster) {
                    return (T) ((JedisCluster) nativeConnection).eval(lua, Collections.singletonList(key), args);
                }

                // 单点
                else if (nativeConnection instanceof RedisProperties.Jedis) {
                    return (T) ((Jedis) nativeConnection).eval(lua, Collections.singletonList(key), args);
                }
                return null;
            }
        });
        return result;
    }



    public void expire(String key, long timeout, TimeUnit unit) {
        try {
            redisTemplate.expire(key, timeout, unit);
        } catch (Exception e) {
            logger.error("设置缓存过期时间失败,key={},timeout={},unit={}", key, timeout, unit, e);
        }
    }

    /**
     * 获取互斥线程等待时间
     *
     * @return
     */
    public long getMutexThreadWaitMilliseconds() {
        return MUTEX_WAIT_MILLISECONDS;
    }

    public Set<String> getKeys(String key) {
        return redisTemplate.keys(key);
    }

    /**
     * 获取随机秒数 如:getRandomTime(30, 7)返回30天到第37天的随机秒数,即时效时间最小为30天,最大为37天
     *
     * @param afterDays N天之后
     * @param rangeDay 日期范围
     * @return java.lang.Long 秒数
     */
    public static Long getRandomTime(int afterDays, int rangeDay) {
        Calendar calendar = Calendar.getInstance();
        long curTime = calendar.getTimeInMillis();
        calendar.add(Calendar.DAY_OF_MONTH, afterDays);
        long minTime = calendar.getTimeInMillis();
        calendar.add(Calendar.DAY_OF_MONTH, rangeDay);
        long maxTime = calendar.getTimeInMillis();
        long randomTime = RandomUtil.randomLong(minTime, maxTime);
        return (randomTime - curTime) / 1000;
    }

    /**
     * 获取30天内的随机秒数
     *
     * @return long 返回1天后30天内的随机秒数
     */
    public static long getRandomTime() {
        return getRandomTime(1, 30);
    }


    public void setnx(String key,String value){


    }


}

说说使用集群的情况。

redis的主从复制。

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master),后者称为从节点(Slave);数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

redis哨兵模式。

哨兵(sentinel):是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的 Master 并将所有 Slave 连接到新的 Master。所以整个运行哨兵的集群的数量不得少于3个节点。

选三台redis搭建集群

 

添加springBootmaven依赖

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--连接池依赖-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<!--web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>

集群yml文件配置

server:
  port: 3035
spring:
  redis:
    # redis哨兵配置
    sentinel:
      # 主节点名称
      master: mymaster
      nodes:
        - 192.168.200.150:6379
        - 192.168.200.150:6380
        - 192.168.200.150:6381
#    # 集群的部署方式
#    cluster:
#      nodes:
#        - 192.168.200.150:6379
#        - 192.168.200.150:6380
#        - 192.168.200.150:6381
#      # #最大重定向次数(由于集群中数据存储在多个节点,所以在访问数据时需要通过转发进行数据定位)
#      max-redirects: 2
#    lettuce:
#      pool:
#        max-idle: 10   # 连接池中的最大空闲连接
#        max-wait: 500   # 连接池最大阻塞等待时间(使用负值表示没有限制)
#        max-active: 8   # 连接池最大连接数(使用负值表示没有限制)
#        min-idle: 0   # 连接池中的最小空闲连接

  # 服务应用名
  application:
    name: redis-cluster
logging:
  pattern:
    console: '%date{yyyy-MM-dd HH:mm:ss.SSS} | %highlight(%5level) [%green(%16.16thread)] %clr(%-50.50logger{49}){cyan} %4line -| %highlight(%msg%n)'
  level:
    root: info
    io.lettuce.core: debug
    org.springframework.data.redis: debug

配置读写分离

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import io.lettuce.core.ReadFrom;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.text.SimpleDateFormat;
import java.util.HashSet;


@Configuration
public class RedisConfiguration {

    /**
     *
     *  配置redis序列化json
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    @Primary    //若有相同类型的Bean时,优先使用此注解标注的Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 为了开发方便,一般直接使用<String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // 配置具体的序列化方式
        // JSON解析任意对象
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        // 设置日期格式
        om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        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;
    }

    /**
     * 配置读写分离
     * @param redisProperties
     * @return
     */
    @Bean
    public RedisConnectionFactory lettuceConnectionFactory(RedisProperties redisProperties) {
        // 配置哨兵节点以及主节点
        RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(
                redisProperties.getSentinel().getMaster(), new HashSet<>(redisProperties.getSentinel().getNodes())
        );

        // 配置读写分离
        LettucePoolingClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
                // 读写分离,这里的ReadFrom是配置Redis的读取策略,是一个枚举,包括下面选择
                // MASTER   仅读取主节点
                // MASTER_PREFERRED   优先读取主节点,如果主节点不可用,则读取从节点
                // REPLICA_PREFERRED   优先读取从节点,如果从节点不可用,则读取主节点
                // REPLICA   仅读取从节点
                // NEAREST   从最近节点读取
                // ANY   从任意一个从节点读取
                .readFrom(ReadFrom.REPLICA_PREFERRED)
                .build();

        return new LettuceConnectionFactory(redisSentinelConfiguration, lettuceClientConfiguration);
    }

}

再使用工具类操作就行。


package com.vinjcent.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

 
@Component
public final class RedisUtils {

    private final RedisTemplate<String, Object> redisTemplate;

    /**
     * 可按自己需求生成"起始时间戳"
     */
    private static final long BEGIN_TIMESTAMP = 1640995200L;

    /**
     * 用于时间戳左移32位
     */
    public static final int MOVE_BITS = 32;

    @Autowired
    public RedisUtils(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    //=============================common===================================

    /**
     * 指定缓存失效时间
     * @param key   键
     * @param time  时间(秒)
     * @return  whether the key has expired
     */
    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;
        }
    }

    /**
     * 指定缓存失效时间(自定义时间单位)
     * @param key   键
     * @param time  时间(秒)
     * @return  whether the key has expired
     */
    public boolean expire(String key, long time, TimeUnit unit){
        try {
            if(time > 0){
                redisTemplate.expire(key, time, unit);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key获取过期时间(默认获取的是秒单位)
     * @param key   键(不能为null)
     * @return      the remaining time, "0" means never expire
     */
    public long getExpire(String key){
        Long time = redisTemplate.getExpire(key, TimeUnit.SECONDS);
        if (time != null) {
            return time;
        }
        return -1L;
    }

    /**
     * 根据key获取过期时间(自定义时间单位)
     * @param key   键(不能为null)
     * @return      the remaining time, "0" means never expire
     */
    public long getExpire(String key, TimeUnit unit){
        Long time = redisTemplate.getExpire(key, unit);
        if (time != null) {
            return time;
        }
        return -1L;
    }

    /**
     * 判断key是否存在
     * @param key   键
     * @return      whether the key exist
     */
    public boolean hasKey(String key) {
        Boolean flag = redisTemplate.hasKey(key);
        try {
            return Boolean.TRUE.equals(flag);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     * @param key   键,可以传递一个值或多个
     */
    public void del(String... key) {
        if(key != null && key.length > 0){
            if (key.length == 1){
                redisTemplate.delete(key[0]);
            }else {
                redisTemplate.delete(Arrays.asList(key));
            }
        }
    }


    //=============================String===================================

    /**
     * 普通缓存获取(泛型)
     * @param key   key键
     * @return      the value corresponding the key
     */
    public Object get(String key){ return key == null ? null : redisTemplate.opsForValue().get(key);}


    /**
     * 普通缓存获取(泛型)
     * @param key   key键
     * @return      the value corresponding the key
     * @param targetType    目标类型
     * @param <T>           目标类型参数
     * @return      the generic value corresponding the key
     */
    public <T> T get(String key, Class<T> targetType){ return key == null ? null : JsonUtils.objParse(redisTemplate.opsForValue().get(key), targetType);}

    /**
     * 普通缓存放入
     * @param key   键
     * @param value 值
     * @return      whether true or 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 值
     * @param time  时间(秒) --- time要大于0,如果time小于0,将设置为无期限
     * @return      whether true or 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,将设置为无期限
     * @param timeUnit  时间单位
     * @return  whether true or false
     */
    public boolean set(String key, Object value, long time, TimeUnit timeUnit){
        try {
            if(time > 0){
                redisTemplate.opsForValue().set(key, value, time, timeUnit);
            }else {
                set(key,value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return      the value after increment
     */
    public long incr(String key, long delta){
        if(delta < 0){
            throw new RuntimeException("递增因子必须大于0");
        }
        Long increment = redisTemplate.opsForValue().increment(key, delta);
        return increment != null ? increment : 0L;
    }

    /**
     * 递减
     * @param key   键
     * @param delta 要增加几(小于0)
     * @return      the value after decrement
     */
    public long decr(String key, long delta){
        if(delta < 0){
            throw new RuntimeException("递减因子必须大于0");
        }
        Long increment = redisTemplate.opsForValue().increment(key, delta);
        return increment != null ? increment : 0L;    }

    //=============================Map===================================

    /**
     * 根据hashKey获取hash列表有多少元素
     * @param key   键(hashKey)
     * @return      the size of map
     */
    public long hsize(String key) {
        try {
            return redisTemplate.opsForHash().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0L;
        }
    }

    /**
     * HashGet  根据"项 中的 键 获取列表"
     * @param key   键(hashKey)能为null
     * @param item  项不能为null
     * @return      the value of the corresponding key
     */
    public Object hget(String key, String item){ return redisTemplate.opsForHash().get(key, item);}

    /**
     * 获取HashKey对应的所有键值
     * @param key   键(hashKey)
     * @return      对应的多个键值
     */
    public Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key);}

    /**
     * 获取HashKey对应的所有键值
     * @param key   键(hashKey)
     * @param keyType   键类型
     * @param valueType 值类型
     * @param <K>       键类型参数
     * @param <V>       值类型参数
     * @return      a map
     */
    public <K, V> Map<K, V> hmget(String key, Class<K> keyType, Class<V> valueType) {
        return JsonUtils.mapParse(redisTemplate.opsForHash().entries(key), keyType, valueType);}

    /**
     * HashSet  存入多个键值对
     * @param key   键(hashKey)
     * @param map   map 对应多个键值对
     */
    public void hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key,map);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * HashSet存入并设置时间
     * @param key   键(hashKey)
     * @param map   对应多个键值
     * @param time  时间(秒)
     * @return  whether true or 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   键(hashKey)
     * @param item  项
     * @param value 值
     * @return  whether true or 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   键(hashKey)
     * @param item  项
     * @param value 值
     * @param time  时间(秒)   注意: 如果已经在hash表有时间,这里将会替换所有的时间
     * @return  whether true or 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;
        }
    }

    /**
     * 放入map集合数据,如果不存在将创建
     * @param key   键(hashKey)
     * @param value map集合
     * @param <K>   map集合键参数类型
     * @param <V>   map集合值参数类型
     * @return  whether true or false
     */
    public <K, V> boolean hsetMap(String key, Map<K, V> value) {
        try {
            redisTemplate.opsForHash().putAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 获取key对应的所有map键值对
     * @param key   键(hashKey)
     * @return      the Map
     */
    public Map<Object, Object> hgetMap(String key) {
        try {
            return redisTemplate.opsForHash().entries(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 获取key对应的所有map键值对(泛型)
     * @param key   键(hashKey)
     * @param keyType   键类型
     * @param valueType 值类型
     * @param <K>       键类型参数
     * @param <V>       值类型参数
     * @return      the Map
     */
    public <K, V> Map<K, V> hgetMap(String key, Class<K> keyType, Class<V> valueType) {
        try {
            return JsonUtils.mapParse(redisTemplate.opsForHash().entries(key), keyType, valueType);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 删除hash表中的值
     * @param key   键(hashKey)   不能为null
     * @param item  项可以是多个    不能为null
     */
    public void hdel(String key, Object... item){
        redisTemplate.opsForHash().delete(key,item);
    }

    /**
     * 判断hash表是否有该项的值
     * @param key   键(hashKey)不能为null
     * @param item  项不能为null
     * @return  whether true or false
     */
    public boolean hHasKey(String key, String item){
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增,如果不存在,就会创建一个,并把新增后的值返回
     * @param key   键(hashKey)
     * @param item  项
     * @param by    要增加几(大于0)
     * @return  the value of the corresponding key after increment in one Map
     */
    public double hincr(String key, String item, double by){
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     * @param key   键(hashKey)
     * @param item  项
     * @param by    要减少几(小于0)
     * @return  the value of the corresponding key after decrement in one Map
     */
    public double hdecr(String key, String item, double by){
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    //=============================Set===================================

    /**
     * 根据key获取Set中的所有值
     * @param key   键
     * @return      all values in one Set
     */
    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  whether true or false
     */
    public boolean sHasKey(String key, Object value){
        try {
            Boolean flag = redisTemplate.opsForSet().isMember(key, value);
            return Boolean.TRUE.equals(flag);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     * @param key       键
     * @param values    值
     * @return  the number of adding successfully
     */
    public long sSet(String key, Object... values){
        try {
            Long nums = redisTemplate.opsForSet().add(key, values);
            return nums != null ? nums : 0L;
        } catch (Exception e) {
            e.printStackTrace();
            return 0L;
        }
    }

    /**
     * 将set数据放入缓存,并设置有效时间
     * @param key       键
     * @param time      时间(秒)
     * @param values    值,可以是多个
     * @return  the number of adding successfully
     */
    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 != null ? count : 0L;
        } catch (Exception e) {
            e.printStackTrace();
            return 0L;
        }
    }

    /**
     * 获取set缓存的长度
     * @param key   键
     * @return      the size of the Set
     */
    public long sGetSetSize(String key){
        try {
            Long size = redisTemplate.opsForSet().size(key);
            return size != null ? size : 0L;
        } catch (Exception e) {
            e.printStackTrace();
            return 0L;
        }
    }

    /**
     * 移除值为values的
     * @param key       键
     * @param values    值(可以是多个)
     * @return          the number of removal
     */
    public long setRemove(String key, Object... values){
        try {
            Long nums = redisTemplate.opsForSet().remove(key, values);
            return nums != null ? nums : 0L;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    //=============================List===================================

    /**
     * 获取list列表数据
     * @param key       键
     * @return          all values of one List
     */
    public List<Object> lget(String key) {
        try {
            return redisTemplate.opsForList().range(key, 0, -1);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     /**
     * 获取list列表数据(泛型)
     * @param key       键
     * @param targetType    目标类型
     * @param <T>           目标类型参数
     * @return      all values of one List
     */
    public <T> List<T> lget(String key, Class<T> targetType) {
        try {
            return JsonUtils.listParse(redisTemplate.opsForList().range(key, 0, -1), targetType);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     * @param key   键
     * @return      the length of the List
     */
    public long lGetListSize(String key){
        try {
            Long size = redisTemplate.opsForList().size(key);
            return size != null ? size : 0L;
        } catch (Exception e) {
            e.printStackTrace();
            return 0L;
        }
    }

    /**
     * 通过索引获取list中的值
     * @param key   键
     * @param index 索引 index >= 0 时, 0:表头, 1:第二个元素,以此类推...    index < 0 时, -1:表尾, -2:倒数第二个元素,以此类推
     * @return  the value of the specified index in one List
     */
    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 index 索引 index >= 0 时, 0:表头, 1:第二个元素,以此类推...    index < 0 时, -1:表尾, -2:倒数第二个元素,以此类推
     * @return  the value of the specified index in one List
     * @param targetType    目标类型
     * @param <T>           目标类型参数
     * @return  the generic value of the specified index in one List
     */
    public <T> T lgetIndex(String key, long index, Class<T> targetType) {
        try {
            return JsonUtils.objParse(redisTemplate.opsForList().index(key, index), targetType);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     * @param key   键
     * @param value 值
     * @return  whether true or false
     */
    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  whether true or false
     */
    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 values    值
     * @return  whether true or false
     */
    public <T> boolean lSet(String key, List<T> values){
        try {
            Long nums = redisTemplate.opsForList().rightPushAll(key, values);
            return nums != null;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list集合放入缓存,并设置有效时间
     * @param key       键
     * @param values    值
     * @param time      时间(秒)
     * @return  whether true or false
     */
    public boolean lSet(String key, List<Object> values, long time){
        try {
            redisTemplate.opsForList().rightPushAll(key, values);
            if (time > 0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     * @param key   键
     * @param value 值
     * @param index 索引
     * @return  whether true or false
     */
    public boolean lUpdateIndex(String key, Object value, long index){
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     * @param key       键
     * @param value     值
     * @param number    移除多少个
     * @return          返回移除的个数
     */
    public long lRemove(String key, Object value, long number){
        try {
            Long count = redisTemplate.opsForList().remove(key, number, value);
            return count != null ? count : 0L;
        } catch (Exception e) {
            e.printStackTrace();
            return 0L;
        }
    }

    //=============================Lock===================================

    /**
     * 解决缓存加锁问题
     * @param key   锁名称
     * @param value 锁值
     * @param timeout   超时时间
     * @param unit  时间单位
     * @param <T>   锁值的数据类型
     * @return  返回加锁成功状态
     */
    public <T> boolean tryLock(String key, T value, long timeout, TimeUnit unit) {
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
        return Boolean.TRUE.equals(flag);
    }

    /**
     * 解决缓存解锁操作
     * @param key   锁名称
     * @return  返回解锁成功状态
     */
    public boolean unLock(String key) {
        Boolean flag = redisTemplate.delete(key);
        return Boolean.TRUE.equals(flag);
    }

    /**
     * 全局生成唯一ID策略
     * 设计: 符号位(1位) - 时间戳(32位) - 序列号(31位)
     * @param keyPrefix     key的前缀
     * @return  返回唯一ID
     */
    public long globalUniqueKey(String keyPrefix) {

        // 1. 生成时间戳
        LocalDateTime now = LocalDateTime.now();

        // 东八区时间
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        // 相减获取时间戳
        long timestamp = nowSecond - BEGIN_TIMESTAMP;

        // 2. 生成序列号(使用日期作为redis自增长超2^64限制,灵活使用年、月、日来存储)
        // 获取当天日期
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 自增长
        Long increment = redisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
        long count = increment != null ? increment : 0L;

        // 3. 拼接并返回(使用二进制或运算)
        return timestamp << MOVE_BITS | count;
    }

}



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

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

相关文章

Tomcat简介

目录 一、Tomcat简介 二、下载安装Tomcat 三、利用Tomcat部署静态页面 一、Tomcat简介 Tomcat是一个HTTP服务器&#xff0c;可以按照HTTP的格式来解析请求来调用用户指定的相关代码然后按照HTTP的格式来构造返回数据。 二、下载安装Tomcat 进入Tomcat官网选择与自己电脑…

电子科技大学人工智能期末复习笔记(二):MDP与强化学习

目录 前言 期望最大搜索&#xff08;Expectimax Search&#xff09; ⭐马尔科夫决策&#xff08;MDP&#xff09;——offline&#xff08;超重点&#xff09; 先来看一个例子 基本概念 政策&#xff08;Policy&#xff09; 折扣&#xff08;Discounting&#xff09; 如…

Mysql中的事务

1. MyIsam是不支持事务的&#xff0c; InnoDB支持 2.事务的四大特性ACID 原子性&#xff08;Atomicity&#xff09;&#xff1a;一个事务中的所有操作&#xff0c;要么全部完成&#xff0c;要么全部不完成&#xff0c;不会结束在中间某个环节&#xff0c;而且事务在执行过程中…

PythonWeb开发基础(四 完)Response使用及wsgify装饰器

课程地址&#xff1a;Python 工程师进阶技术图谱 文章目录&#x1f33e; Response使用及wsgify装饰器1、Response的使用2、wsgify装饰器&#x1f33e; Response使用及wsgify装饰器 1、Response的使用 前面一节我们知道了&#xff0c;使用webob的Request模块可以很方便地对请求…

若依框架---PageHelper分页(十五)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是小童&#xff0c;Java开发工程师&#xff0c;CSDN博客博主&#xff0c;Java领域新星创作者 &#x1f4d5;系列专栏&#xff1a;前端、Java、Java中间件大全、微信小程序、微信支付、若依框架、Spring全家桶 &#x1f4…

STM32开发(7)----CubeMX配置串口通讯(轮询方式)

CubeMX配置串口通讯&#xff08;轮询方式&#xff09;前言一、串口的介绍二、实验过程1.实验材料2.STM32CubeMX配置PWM3.代码实现重载printf轮询接收4.编译烧录5.硬件连接6.实验结果重载printf结果串口轮询接收结果总结前言 本章介绍使用STM32CubeMX对串口进行配置的方法&…

​力扣解法汇总1797. 设计一个验证系统

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;力扣 描述&#xff1a; 你需要设计一个包含验证码的验证系统。每一次验证中&#xff0c;用户会收到一个…

vue:自定义组件如何使用v-model

问题&#xff1a; 在使用自定义组件时&#xff0c;我们有时候需要使用 v-model 来双向绑定。方法&#xff1a; 在vue中&#xff0c;v-model 的值相当于默认传递了一个名为 value 的 prop 和一个名为 input 的事件&#xff08;注意&#xff0c;这个value的prop是需要在自定义组件…

《皮格马利翁效应》-期待效应

“ 人生没有白走的路&#xff0c;没有白读的书&#xff0c;你触碰过的那些文字会在不知不觉中帮你认知这个世界 #每天读本书 #关注我每天解读一本书”《皮格马利翁效应》01关于作者朱瑟琳•乔塞尔森&#xff0c;女性心理学家&#xff0c;是国际 公认的精神医学大师欧文•亚隆的…

刚刚,微软推出支持chatGPT的必应

文章目录刚刚&#xff0c;微软推出支持chatGPT的必应什么是新的必应&#xff1f;如何体验页面初体验结语&#xff1a;搜索引擎的新时代刚刚&#xff0c;微软推出支持chatGPT的必应 这款新的必应功能允许用户通过自然语言方式与chatgpt进行交流&#xff0c;以获得快速、准确的信…

华为HCIE学习之openstack基础

文章目录一、Openstack各种文件位置二、Openstack命令操作1.使用帮助三、用命令发放云主机1、创建租户2、创建用户并与租户绑定3、注册镜像4、创建规格5、创建公有网络及其子网&#xff08;做弹性IP用&#xff09;6、创建私有网络及其子网7、创建路由并设置网关与端口8、创建安…

C++类与对象(中)

✅<1>主页&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;C &#x1f525;<3>创作者&#xff1a;我的代码爱吃辣 ☂️<4>开发环境&#xff1a;Visual Studio 2022 &#x1f4ac;<5>前言&#xff1a;C类中一共有六个默认成员函…

当ChatGPT遇到网络安全

ChatGPT&#xff1a;是人工智能技术驱动的自然语言处理工具&#xff0c;它能够通过学习和理解人类的语言来进行对话&#xff0c;还能根据聊天的上下文进行互动&#xff0c;真正像人类一样来聊天交流&#xff0c;甚至能完成撰写邮件、视频脚本、文案、翻译、代码等任务。GPT 是 …

活动星投票文艺巡演活动免费投票程序制作网页投票网站

“文艺巡演活动”网络评选投票_线上小程序的投票方式_视频投票的功能_在线投票程序用户在使用微信投票的时候&#xff0c;需要功能齐全&#xff0c;又快捷方便的投票小程序。而“活动星投票”这款软件使用非常的方便&#xff0c;用户可以随时使用手机微信小程序获得线上投票服务…

ConcurrentHashMap底层原理介绍

概述: ConcurrentHashMap相对于HashMap的性能较差一些,但相比于Hashtable而言性能要高很多,因为Hashtable内部的所有方法都是同步方法,加了synchronized锁,所以性能上比较差,但在多线程环境下是具有很强的安全性的ConcurrentHashMap避免了对全局加锁改成了局部加锁操作&#xf…

Go最新版下载 Go1.20版新特性

Go官方正式发布了Go1.20稳定版 该版本依然保持 Go1 兼容性&#xff0c;可以升级到 Go1.20&#xff0c;而不需要做任何代码改动。 可以使用你任何喜欢的方式升级&#xff1a; 比如&#xff1a; go install golang.org/dl/go1.20latest 具体的可以参考官网教程&#xff1a; ht…

简介DNS协议、ICMP协议、NAT技术

文章目录一、DNS 协议1.背景2.域名二、ICMP 协议1.功能2.格式3. ping 命令4. traceroute 命令三、NAT 技术1.基本理解2. NAT 转换过程3. NAPT4. NAT 技术的缺陷5. NAT 和代理服务器一、DNS 协议 DNS&#xff08;Domain Name System&#xff0c;域名系统&#xff09;&#xff0…

【头歌】串的运算及应用

串的运算及应用第1关&#xff1a;求子串任务描述本关任务&#xff1a;实现字符串的求子串操作。相关知识为了完成本关任务&#xff0c;你需要理解&#xff1a;1. 求子串操作&#xff0c;2.字符串的顺序存储。求子串操作从一个字符串S的某个位置开始截取若干个连续字符&#xff…

采样电路的3个组成部分

采样电路的使用实际上是电路的一个闭环控制过程&#xff0c;也可以理解为一个负反馈过程&#xff0c;采集的信号被传送到主控制芯片进行调整。今天就来为您介绍一下采样电路的三个组成部分分析&#xff01;一起来看看吧&#xff01; 这里的采样实际上分为电流采样、电压采样、…

((蓝桥杯 刷题全集)【备战(蓝桥杯)算法竞赛-第6天(动态规划 专题)】( 从头开始重新做题,记录备战竞赛路上的每一道题 )距离蓝桥杯还有61天

&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6; 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&a…