手写Redis分布式锁+RedisUtil二次封装

文章目录

    • 1.手写Redis分布式锁
        • 1.RedisShareLockUtil
        • 2.使用方式
    • 2.RedisUtil二次封装
        • 1.RedisUtil
        • 2.使用案例

1.手写Redis分布式锁

1.RedisShareLockUtil
package com.sunxiansheng.redis.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Component
public class RedisShareLockUtil {

    private static final Logger logger = LoggerFactory.getLogger(RedisShareLockUtil.class);
    private static final long DEFAULT_TIMEOUT = 10; // 默认超时时间(秒)
    private static final String DEFAULT_LOCK_NAME_PREFIX = "lock:"; // 默认锁名前缀

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 尝试获取锁
     * @param lockKey 锁的key
     * @param timeout 超时时间(秒)
     * @return 锁的value,用于释放锁
     */
    public String tryLock(String lockKey, long timeout) {
        String value = UUID.randomUUID().toString();
        Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, value, timeout, TimeUnit.SECONDS);
        while (locked == null || !locked) {
            if (locked == null) {
                logger.error("尝试获取锁时出现错误,可能是 Redis 连接问题,key: {}", lockKey);
            } else {
                logger.warn("获取锁失败,重试中,key: {}", lockKey);
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                logger.error("尝试获取锁时线程被中断,key: {}", lockKey, e);
                return null;
            }
            locked = redisTemplate.opsForValue().setIfAbsent(lockKey, value, timeout, TimeUnit.SECONDS);
        }
        logger.info("成功获取锁,key: {}", lockKey);
        return value;
    }

    /**
     * 释放锁
     * @param lockKey 锁的key
     * @param value 锁的value
     */
    public boolean releaseLock(String lockKey, String value) {
        String scriptText = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(scriptText);
        script.setResultType(Long.class);
        Long result = redisTemplate.execute(script, Collections.singletonList(lockKey), value);
        if (result != null && result > 0) {
            logger.info("成功释放锁,key: {}", lockKey);
            return true;
        } else {
            if (result == null) {
                logger.error("释放锁失败,Redis 执行错误,key: {}", lockKey);
            } else {
                logger.warn("释放锁失败,锁不存在或锁的值不匹配,key: {}", lockKey);
            }
            return false;
        }
    }


    /**
     * 使用锁执行操作,使用默认超时时间
     * @param lockKey 锁的key
     * @param action 要执行的操作
     */
    public void executeWithLock(String lockKey, Runnable action) {
        executeWithLock(lockKey, DEFAULT_TIMEOUT, action);
    }

    /**
     * 使用锁执行操作
     * @param lockKey 锁的key
     * @param timeout 超时时间(秒)
     * @param action 要执行的操作
     */
    public void executeWithLock(String lockKey, long timeout, Runnable action) {
        String fullLockKey = DEFAULT_LOCK_NAME_PREFIX + lockKey;
        String value = tryLock(fullLockKey, timeout);
        if (value != null) {
            try {
                action.run();
            } finally {
                releaseLock(fullLockKey, value);
            }
        } else {
            logger.warn("无法获取锁,key: {}", fullLockKey);
        }
    }
}
2.使用方式
    @RequestMapping("/testLock")
    public void testLock() {
        // 默认超时时间10s,自定义锁名字
        redisShareLockUtil.executeWithLock("mylock", () -> {
            // 需要加锁的逻辑
        });
        // 
        // 自定义超时时间30s,自定义锁名字
        redisShareLockUtil.executeWithLock("mylock", 30, () -> {
            // 需要加锁的逻辑
        });
    }

2.RedisUtil二次封装

1.RedisUtil
package com.sunxiansheng.redis.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Description: RedisUtil工具类
 * @Author sun
 * @Create 2024/6/5 14:17
 * @Version 1.0
 */
@Component
public class RedisUtil {

    private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private static final String CACHE_KEY_SEPARATOR = ".";

    /**
     * 构建缓存key
     * @param strObjs 多个字符串拼接成缓存key
     * @return 拼接后的缓存key
     * @example
     * <pre>
     *     String key = redisUtil.buildKey("user", "123");
     *     System.out.println(key);  // 输出: user.123
     * </pre>
     */
    public String buildKey(String... strObjs) {
        return String.join(CACHE_KEY_SEPARATOR, strObjs);
    }

    // =============================Common============================

    /**
     * 是否存在key
     * @param key Redis中的key
     * @return true如果key存在,否则false
     * @example
     * <pre>
     *     boolean exists = redisUtil.exists("myKey");
     *     System.out.println(exists);
     * </pre>
     */
    public boolean exists(String key) {
        return execute(() -> redisTemplate.hasKey(key));
    }

    /**
     * 删除key
     * @param key Redis中的key
     * @return true如果删除成功,否则false
     * @example
     * <pre>
     *     boolean deleted = redisUtil.delete("myKey");
     *     System.out.println(deleted);
     * </pre>
     */
    public boolean delete(String key) {
        return execute(() -> redisTemplate.delete(key));
    }

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

    /**
     * 设置key-value对
     * @param key Redis中的key
     * @param value 要设置的值
     * @example
     * <pre>
     *     redisUtil.set("myKey", "myValue");
     * </pre>
     */
    public void set(String key, Object value) {
        execute(() -> {
            redisTemplate.opsForValue().set(key, value);
            return null;
        });
    }

    /**
     * 设置key-value对,并设置过期时间
     * @param key Redis中的key
     * @param value 要设置的值
     * @param timeout 过期时间
     * @param unit 时间单位
     * @example
     * <pre>
     *     redisUtil.set("myKey", "myValue", 10, TimeUnit.MINUTES);
     * </pre>
     */
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        execute(() -> {
            redisTemplate.opsForValue().set(key, value, timeout, unit);
            return null;
        });
    }

    /**
     * 设置key-value对,如果key不存在,则设置成功,并指定过期时间
     * @param key Redis中的key
     * @param value 要设置的值
     * @param timeout 过期时间
     * @param unit 时间单位
     * @return true如果设置成功,否则false
     * @example
     * <pre>
     *     boolean result = redisUtil.setIfAbsent("myKey", "myValue", 10, TimeUnit.MINUTES);
     *     System.out.println(result);
     * </pre>
     */
    public boolean setIfAbsent(String key, Object value, long timeout, TimeUnit unit) {
        return execute(() -> redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit));
    }

    /**
     * 获取指定key的值
     * @param key Redis中的key
     * @param clazz 值的类型
     * @return key对应的值
     * @example
     * <pre>
     *     String value = redisUtil.get("myKey", String.class);
     *     System.out.println(value);
     * </pre>
     */
    public <T> Optional<T> get(String key, Class<T> clazz) {
        return Optional.ofNullable(execute(() -> clazz.cast(redisTemplate.opsForValue().get(key))));
    }

    /**
     * 递增
     * @param key Redis中的key
     * @param delta 增量
     * @example
     * <pre>
     *     redisUtil.increment("myKey", 1);
     * </pre>
     */
    public void increment(String key, long delta) {
        execute(() -> {
            redisTemplate.opsForValue().increment(key, delta);
            return null;
        });
    }

    // =============================Hash============================

    /**
     * 向hash中存入数据
     * @param key Redis中的key
     * @param hashKey hash中的小key
     * @param value hash中的小value
     * @example
     * <pre>
     *     redisUtil.hPut("myHash", "field1", "value1");
     * </pre>
     */
    public void hPut(String key, String hashKey, Object value) {
        execute(() -> {
            redisTemplate.opsForHash().put(key, hashKey, value);
            return null;
        });
    }

    /**
     * 获取hash中的数据
     * @param key Redis中的key
     * @param hashKey hash中的小key
     * @param clazz 值的类型
     * @return hash中的小value
     * @example
     * <pre>
     *     String value = redisUtil.hGet("myHash", "field1", String.class);
     *     System.out.println(value);
     * </pre>
     */
    public <T> Optional<T> hGet(String key, String hashKey, Class<T> clazz) {
        return Optional.ofNullable(execute(() -> clazz.cast(redisTemplate.opsForHash().get(key, hashKey))));
    }

    /**
     * 获取hash中的所有数据
     * @param key Redis中的key
     * @return hash中的所有数据
     * @example
     * <pre>
     *     Map<Object, Object> map = redisUtil.hGetAll("myHash");
     *     map.forEach((k, v) -> System.out.println(k + ": " + v));
     * </pre>
     */
    public Optional<Map<Object, Object>> hGetAll(String key) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForHash().entries(key)));
    }

    /**
     * 删除hash中的指定字段
     * @param key Redis中的key
     * @param hashKey hash中的小key
     * @example
     * <pre>
     *     redisUtil.hDelete("myHash", "field1");
     * </pre>
     */
    public void hDelete(String key, Object... hashKey) {
        execute(() -> {
            redisTemplate.opsForHash().delete(key, hashKey);
            return null;
        });
    }

    /**
     * 获取并删除hash中的所有数据
     * @param key Redis中的key
     * @return hash中的所有数据
     * @example
     * <pre>
     *     Map<Object, Object> map =redisUtil.hGetAndDelete(“myHash”);
     *     map.forEach((k, v) -> System.out.println(k + “: “ + v));
     * </pre>
     */
    public Optional<Map<Object, Object>> hGetAndDelete(String key) {
        Map<Object, Object> map = new HashMap<>();
        try (Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(key, ScanOptions.NONE)) {
            while (cursor.hasNext()) {
                Map.Entry<Object, Object> entry = cursor.next();
                Object hashKey = entry.getKey();
                Object hashValue = entry.getValue();
                map.put(hashKey, hashValue);
                redisTemplate.opsForHash().delete(key, hashKey);
            }
        } catch (Exception e) {
            logger.error("Redis hGetAndDelete error: key={}", key, e);
        }
        return Optional.of(map);
    }
// =============================List============================

    /**
     * 向list中左侧推入数据
     * @param key Redis中的key
     * @param value list中的值
     * @example
     * <pre>
     *     redisUtil.lPush("myList", "value1");
     * </pre>
     */
    public void lPush(String key, Object value) {
        execute(() -> {
            redisTemplate.opsForList().leftPush(key, value);
            return null;
        });
    }

    /**
     * 向list中右侧推入数据
     * @param key Redis中的key
     * @param value list中的值
     * @example
     * <pre>
     *     redisUtil.rPush("myList", "value1");
     * </pre>
     */
    public void rPush(String key, Object value) {
        execute(() -> {
            redisTemplate.opsForList().rightPush(key, value);
            return null;
        });
    }

    /**
     * 从list中左侧弹出数据
     * @param key Redis中的key
     * @param clazz 值的类型
     * @return list中的值
     * @example
     * <pre>
     *     String value = redisUtil.lPop("myList", String.class).orElse(null);
     *     System.out.println(value);
     * </pre>
     */
    public <T> Optional<T> lPop(String key, Class<T> clazz) {
        return Optional.ofNullable(execute(() -> clazz.cast(redisTemplate.opsForList().leftPop(key))));
    }

    /**
     * 从list中右侧弹出数据
     * @param key Redis中的key
     * @param clazz 值的类型
     * @return list中的值
     * @example
     * <pre>
     *     String value = redisUtil.rPop("myList", String.class).orElse(null);
     *     System.out.println(value);
     * </pre>
     */
    public <T> Optional<T> rPop(String key, Class<T> clazz) {
        return Optional.ofNullable(execute(() -> clazz.cast(redisTemplate.opsForList().rightPop(key))));
    }

    /**
     * 获取list中的指定范围的数据
     * @param key Redis中的key
     * @param start 起始位置
     * @param end 结束位置
     * @return list中的值
     * @example
     * <pre>
     *     List<Object> list = redisUtil.lRange("myList", 0, -1).orElse(Collections.emptyList());
     *     list.forEach(System.out::println);
     * </pre>
     */
    public Optional<List<Object>> lRange(String key, long start, long end) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForList().range(key, start, end)));
    }

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

    /**
     * 向set中添加数据
     * @param key Redis中的key
     * @param values set中的值
     * @example
     * <pre>
     *     redisUtil.sAdd("mySet", "value1", "value2");
     * </pre>
     */
    public void sAdd(String key, Object... values) {
        execute(() -> {
            redisTemplate.opsForSet().add(key, values);
            return null;
        });
    }

    /**
     * 获取set中的所有数据
     * @param key Redis中的key
     * @return set中的所有值
     * @example
     * <pre>
     *     Set<Object> set = redisUtil.sMembers("mySet").orElse(Collections.emptySet());
     *     set.forEach(System.out::println);
     * </pre>
     */
    public Optional<Set<Object>> sMembers(String key) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForSet().members(key)));
    }

    /**
     * 判断set中是否存在指定的值
     * @param key Redis中的key
     * @param value set中的值
     * @return true如果存在,否则false
     * @example
     * <pre>
     *     boolean exists = redisUtil.sIsMember("mySet", "value1").orElse(false);
     *     System.out.println(exists);
     * </pre>
     */
    public Optional<Boolean> sIsMember(String key, Object value) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForSet().isMember(key, value)));
    }

    /**
     * 从set中随机弹出一个值
     * @param key Redis中的key
     * @return set中的值
     * @example
     * <pre>
     *     Object value = redisUtil.sPop("mySet").orElse(null);
     *     System.out.println(value);
     * </pre>
     */
    public Optional<Object> sPop(String key) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForSet().pop(key)));
    }

    /**
     * 获取set的大小
     * @param key Redis中的key
     * @return set的大小
     * @example
     * <pre>
     *     long size = redisUtil.sCard("mySet").orElse(0L);
     *     System.out.println(size);
     * </pre>
     */
    public Optional<Long> sCard(String key) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForSet().size(key)));
    }

// =============================ZSet============================

    /**
     * 向有序集合中添加元素
     * @param key Redis中的key
     * @param value 元素的值
     * @param score 元素的分数
     * @return true如果添加成功,否则false
     * @example
     * <pre>
     *     boolean added = redisUtil.zAdd("myZSet", "value1", 1.0).orElse(false);
     *     System.out.println(added);
     * </pre>
     */
    public Optional<Boolean> zAdd(String key, Object value, double score) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().add(key, value, score)));
    }

    /**
     * 获取有序集合的元素数量
     * @param key Redis中的key
     * @return 元素数量
     * @example
     * <pre>
     *     long size = redisUtil.zCard("myZSet").orElse(0L);
     *     System.out.println(size);
     * </pre>
     */
    public Optional<Long> zCard(String key) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().size(key)));
    }

    /**
     * 获取有序集合指定范围内的元素
     * @param key Redis中的key
     * @param start 起始位置
     * @param end 结束位置
     * @return 指定范围内的元素集合
     * @example
     * <pre>
     *     Set<Object> set = redisUtil.zRange("myZSet", 0, -1).orElse(Collections.emptySet());
     *     set.forEach(System.out::println);
     * </pre>
     */
    public Optional<Set<Object>> zRange(String key, long start, long end) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().range(key, start, end)));
    }

    /**
     * 删除有序集合中的指定元素
     * @param key Redis中的key
     * @param value 要删除的元素
     * @return 被删除的元素数量
     * @example
     * <pre>
     *     long removed = redisUtil.zRemove("myZSet", "value1").orElse(0L);
     *     System.out.println(removed);
     * </pre>
     */
    public Optional<Long> zRemove(String key, Object value) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().remove(key, value)));
    }

    /**
     * 获取有序集合中指定元素的分数
     * @param key Redis中的key
     * @param value 元素的值
     * @return 元素的分数
     * @example
     * <pre>
     *     double score = redisUtil.zScore("myZSet", "value1").orElse(null);
     *     System.out.println(score);
     * </pre>
     */
    public Optional<Double> zScore(String key, Object value) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().score(key, value)));
    }

    /**
     * 获取有序集合中指定分数范围内的元素
     * @param key Redis中的key
     * @param start 起始分数
     * @param end 结束分数
     * @return 指定分数范围内的元素集合
     * @example
     * <pre>
     *     Set<Object> set = redisUtil.zRangeByScore("myZSet”, 0, 100).orElse(Collections.emptySet());
     *     set.forEach(System.out::println);
     *
     */
    public Optional<Set> zRangeByScore(String key, double start, double end) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().rangeByScore(key, start, end)));
    }

    /**
     * 增加有序集合中指定元素的分数
     * @param key Redis中的key
     * @param value 元素的值
     * @param score 增加的分数
     * @return 增加后的分数
     * @example
     * <pre>
     *     double newScore = redisUtil.zIncrementScore("myZSet", "value1", 10.0).orElse(null);
     *     System.out.println(newScore);
     * </pre>
     */
    public Optional<Double> zIncrementScore(String key, Object value, double score) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().incrementScore(key, value, score)));
    }

    /**
     * 获取有序集合中指定元素的排名
     * @param key Redis中的key
     * @param value 元素的值
     * @return 元素的排名
     * @example
     * <pre>
     *     long rank = redisUtil.zRank("myZSet", "value1").orElse(null);
     *     System.out.println(rank);
     * </pre>
     */
    public Optional<Long> zRank(String key, Object value) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().rank(key, value)));
    }

    /**
     * 从有序集合中按分数范围获取成员及其分数
     * @param key 排行榜的key
     * @param start 起始位置(包含)
     * @param end 结束位置(包含)
     * @return Set<ZSetOperations.TypedTuple < Object>> 每个TypedTuple对象包含以下内容:value: 集合中的成员,score: 成员的分数。
     * @example
     * <pre>
     *     Set<ZSetOperations.TypedTuple<Object>> set = redisUtil.zRangeWithScores("myZSet", 0, 100).orElse(Collections.emptySet());
     *     set.forEach(tuple -> System.out.println(tuple.getValue() + ": " + tuple.getScore()));
     * </pre>
     */
    public Optional<Set<ZSetOperations.TypedTuple<Object>>> zRangeWithScores(String key, long start, long end) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().rangeWithScores(key, start, end)));
    }

    /**
     * 获取有序集合中指定分数范围内的成员及其分数
     * @param key Redis中的key
     * @param min 最小分数
     * @param max 最大分数
     * @return Set<ZSetOperations.TypedTuple < Object>> 每个TypedTuple对象包含以下内容:value: 集合中的成员,score: 成员的分数。
     * @example
     * <pre>
     *     Set<ZSetOperations.TypedTuple<Object>> set = redisUtil.zRangeByScoreWithScores("myZSet", 0, 100).orElse(Collections.emptySet());
     *     set.forEach(tuple -> System.out.println(tuple.getValue() + ": " + tuple.getScore()));
     * </pre>
     */
    public Optional<Set<ZSetOperations.TypedTuple<Object>>> zRangeByScoreWithScores(String key, double min, double max) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max)));
    }

    /**
     * 获取有序集合中指定成员的分数范围排名
     * @param key Redis中的key
     * @param value 成员的值
     * @return 成员的分数排名
     * @example
     * <pre>
     *     long rank = redisUtil.zRevRank("myZSet", "value1").orElse(null);
     *     System.out.println(rank);
     * </pre>
     */
    public Optional<Long> zRevRank(String key, Object value) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().reverseRank(key, value)));
    }

    /**
     * 获取有序集合中指定分数范围内的元素数量
     * @param key Redis中的key
     * @param min 最小分数
     * @param max 最大分数
     * @return 元素数量
     * @example
     * <pre>
     *     long count = redisUtil.zCount("myZSet", 0, 100).orElse(0L);
     *     System.out.println(count);
     * </pre>
     */
    public Optional<Long> zCount(String key, double min, double max) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().count(key, min, max)));
    }

    /**
     * 移除有序集合中指定分数范围内的元素
     * @param key Redis中的key
     * @param min 最小分数
     * @param max 最大分数
     * @return 移除的元素数量
     * @example
     * <pre>
     *     long removed = redisUtil.zRemoveByScore("myZSet", 0, 100).orElse(0L);
     *     System.out.println(removed);
     * </pre>
     */
    public Optional<Long> zRemoveByScore(String key, double min, double max) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().removeRangeByScore(key, min, max)));
    }

    /**
     * 移除有序集合中指定排名范围内的元素
     * @param key Redis中的key
     * @param start 起始排名
     * @param end 结束排名
     * @return 移除的元素数量
     * @example
     * <pre>
     *     long removed = redisUtil.zRemoveByRank("myZSet", 0, 100).orElse(0L);
     *     System.out.println(removed);
     * </pre>
     */
    public Optional<Long> zRemoveByRank(String key, long start, long end) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().removeRange(key, start, end)));
    }

    private <T> T execute(RedisOperation<T> operation) {
        try {
            return operation.execute();
        } catch (Exception e) {
            logger.error("Redis operation error", e);
            return null;
        }
    }

    @FunctionalInterface
    private interface RedisOperation<T> {
        T execute();
    }
}
2.使用案例
    @RequestMapping("/testRedisUtil")
    public String testRedisUtil() {
        // 设置key-value
        redisUtil.set("testRedisUtil", "123456");
        // 获取key对应的value并指定转换的类型
        String res = redisUtil.get("testRedisUtil", String.class).orElse(null);
        if (res == null) {
            return "null";
        }
        return res;
    }

CleanShot 2024-07-20 at 11.08.21@2x

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

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

相关文章

Qt WORD/PDF(一)使用 QtPdfium库实现 PDF 预览

文章目录 一、简介二、下载 QtPdfium三、加载 QtPdfium 动态库四、Demo 使用 关于QT Widget 其它文章请点击这里: QT Widget 国际站点 GitHub: https://github.com/chenchuhan 国内站点 Gitee : https://gitee.com/chuck_chee 姊妹篇: Qt WORD/PDF&#x…

IO的入门

目录 1.IO概述1.1流的分类 2.字符流2.1 案例 1.IO概述 IO&#xff08;Input/Output&#xff09;:输入和输出&#xff0c;指的是某个设备或环境进行数据的输入或者输出。例如&#xff1a;键盘的输入&#xff0c;再比如显示器就是输出设备&#xff0c;输出图像。 对于java来说输…

el-table表格嵌套子表格:展开所有内容;对当前展开行内容修改,当前行默认展开;

原文1 原文2 原文3 一、如果全部展开 default-expand-all"true" 二、设置有数据的行打开下拉 1、父table需要绑定两个属性expand-row-key和row-key <el-table:data"tableData":expand-row-keys"expends" //expends是数组&#xff0c;设置…

canal详解及demo

提示&#xff1a;如何保证Redis中的数据与数据库中的数据一致性&#xff1f;数据同步canal的介绍和demo、大型企业如何实现mysql到redis的同步&#xff1f;使用binlog实时更新redis缓存、canal的接入教程、win下canal的服务器端、canal客户端的创建、连接、测试教程、数据同步方…

平方根无迹卡尔曼滤波(SR-UKF)的MATLAB例程,使用三维非线性的系统

本MATLAB 代码实现了平方根无迹卡尔曼滤波&#xff08;SR-UKF&#xff09;算法&#xff0c;用于处理三维非线性状态估计问题 文章目录 运行结果代码概述代码 运行结果 三轴状态曲线对比&#xff1a; 三轴误差曲线对比&#xff1a; 误差统计特性输出&#xff08;命令行截图&…

汇编DOSBox 如何使文件可以运行

1.在vscode编写&#xff08;其他也可以&#xff09;如何在vscode中编写汇编语言并在终端进行调试(保姆级别&#xff09;_如何在vscode编译asm-CSDN博客 2.点击ML615中的DOS 2.1在命令行中输入命令 ml 文件名.asm ml 文件名.obj 2.2 将生成的exe文件移动到Assembly里面 这个文件…

QT多线程(三):基于条件等待的线程同步

在多线程的程序中&#xff0c;多个线程之间的同步问题实际上就是多个线程之间的协调问题。例如在以下例子中只有等 ThreadDAQ 写满一个缓冲区之后&#xff0c;ThreadShow 和ThreadSaveFile 才能读取缓冲区的数据。 int buffer[100]; QReadWriteLock Lock; //定义读写锁变量 v…

js 数组方法总结

在 JavaScript 中&#xff0c;数组有许多内置的方法&#xff0c;可以用于操作和处理数组。以下是一些常用的数组方法及其特点&#xff1a; 1. push() - 用途&#xff1a;向数组末尾添加一个或多个元素 - 改变原数组&#xff1a;是 - 返回值&#xff1a;返回数组的新长度 let ar…

MongoDB-副本集

一、什么是 MongoDB 副本集&#xff1f; 1.副本集的定义 MongoDB 的副本集&#xff08;Replica Set&#xff09;是一组 MongoDB 服务器实例&#xff0c;它们存储同一数据集的副本&#xff0c;确保数据的高可用性和可靠性。副本集中的每个节点都有相同的数据副本&#xff0c;但…

驱动开发-入门【1】

1.内核下载地址 Linux内核源码的官方网站为https://www.kernel.org/&#xff0c;可以在该网站下载最新的Linux内核源码。进入该网站之后如下图所示&#xff1a; 从上图可以看到多个版本的内核分支&#xff0c;分别为主线版本&#xff08;mainline&#xff09;、稳定版本&#…

数字电视标准与分类

数字电视相关内容是一个极其成熟且久远的领域&#xff0c;并不像其它的技术方面那么前沿。但是学习技术的另外一个方面也不就是可以维持咱们的好奇心以及认识生活中多个事务后面的技术本质。 近年来&#xff0c;电视领域发生了一系列的变化&#xff0c;电视数字化的进程明显加快…

【WRF安装】WRF编译错误总结1:HDF5库包安装

目录 1 HDF5库包安装有误&#xff1a;HDF5 not set in environment. Will configure WRF for use without.HDF5的重新编译 错误原因1&#xff1a;提示 overflow 错误1. 检查系统是否缺少依赖库或工具2. 检查和更新编译器版本3. 检查 ./configure 报错信息4. 检查系统环境变量5.…

51c嵌入式~单片机~合集3

我自己的原文哦~ https://blog.51cto.com/whaosoft/12362395 一、STM32代码远程升级之IAP编程 IAP是什么 有时项目上需要远程升级单片机程序&#xff0c;此时需要接触到IAP编程。 IAP即为In Application Programming&#xff0c;解释为在应用中编程&#xff0c;用户自己的…

LeetCode 11. 盛最多水的容器(超简单讲解)

11. 盛最多水的容器 题目示例示例1示例2 解题思路双指针实现设计 详细代码 题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多…

Spring Boot 集成 Elasticsearch怎样在不启动es的情况下正常启动服务

解释 在spingboot 集成es客户端后&#xff0c;每当服务启动时&#xff0c;服务默认都会查看es中是否已经创建了对应的索引&#xff0c;如果没有索引则创建。基于上面的规则我们可以通过配置不自动创建索引来达到在没有es服务的情况下正常启动服务。 解决办法 在entity类的Docu…

IOTIQS100芯片, TCP 发送数据+NSOSD,data要是hex16进制转换方法

命令&#xff1a;data以十六进制字符串格式发送的数据。 方法 代码 sprintf(temp, "%02X", data[i]);&#xff1a;将当前字节转换为两位宽的大写十六进制字符&#xff0c;并存储在 temp 中。如果需要小写字母&#xff0c;可以将格式说明符改为 "%02x"。 …

Python的3D可视化库【vedo】2-3 (plotter模块) 增删物体、控制相机

文章目录 4 Plotter类的方法4.3 渲染器内的物体操作4.3.1 添加物体4.3.2 移除物体4.3.3 渲染器的内容列表 4.4 相机控制4.4.1 访问相机对象4.4.2 重置相机状态4.4.3 移动相机位置4.4.4 改变相机焦点4.4.5 改变相机朝向的平面4.4.5 旋转相机4.4.6 对齐相机的上朝向4.4.7 缩放 ve…

Mumu模拟器12开启ADB调试方法

在使用安卓模拟器进行开发或调试时&#xff0c;ADB&#xff08;Android Debug Bridge&#xff09;是一项不可或缺的工具。大多数模拟器默认开启了ADB调试功能&#xff0c;但在安装最新版的 Mumu模拟器12 时&#xff0c;可能会遇到 adb devices 无法识别设备的问题。 问题描述 …

【OpenCV计算机视觉】图像处理——平滑

本篇文章记录我学习【OpenCV】图像处理中关于“平滑”的知识点&#xff0c;希望我的分享对你有所帮助。 目录 一、什么是平滑处理 1、平滑的目的是什么&#xff1f; 2、常见的图像噪声 &#xff08;1&#xff09;椒盐噪声 ​编辑&#xff08;2&#xff09; 高斯噪声 &a…

vue CSS 自定义宽高 翻页 剥离 效果

新增需求&#xff0c;客户需要类似PPT的剥离效果用于WEB页面翻页&#xff0c;查找资料后&#xff0c;参考下方的掘金博主的文章&#xff0c;并将HTML修改成vue的页面进行使用。其中宽度、高度改成了变量&#xff0c;样式style中的属性与宽高的关系整理成了公式进行动态计算。 …