Redis 常用数据类型插入性能对比:循环插入 vs. 批量插入

news2024/11/25 21:04:52

Redis 是一款高性能的键值数据库,其支持多种数据类型(String、Hash、List、Set、ZSet、Geo)。在开发中,经常会遇到需要插入大量数据的场景。如果逐条插入,性能会显得较低,而采用 Pipeline 批量插入 能大幅提升插入效率。本文将基于常见的 Redis 数据类型,对比循环插入与批量插入的性能差异。 以下测试结果仅供参考,真实环境请多测试! 


1. 测试环境说明

  • Redis 版本:3.x、5.x 或更高
  • Spring Boot 环境spring-boot-starter-data-redis

测试过程中,我们将模拟插入 1 万 / 10 万条数据,并记录执行时间。以下是常见的 Redis 数据类型及其插入方法。


2. 各数据类型的插入实现

2.1 String 类型
  • 循环插入:

逐条使用 opsForValue().set() 方法进行插入:

    /**
     * String类型测试循环插入性能
     * @param dataCount
     */
    public void testInsertPerformanceStr(int dataCount) {
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < dataCount; i++) {
            stringRedisTemplate.opsForValue().set(StrKey+i, "value" + i);
        }
        log.info("使用String类型循环插入{}条数据,总耗时为: {} ms",dataCount, System.currentTimeMillis() - beginTime);

    }

  • 批量插入:

通过 Redis Pipeline 批量操作提升性能:

    /**
     * String类型测试批量处理插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testBatchInsertOptimizedStr(int dataCount) {
        // 开始计时
        long startTime = System.currentTimeMillis();

        // 使用 Redis Pipeline 进行批量插入
        stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (int i = 0; i < dataCount; i++) {
                // 构造键值对
                String key = StrKey + i;
                String value = "value" + i;

                // 将命令加入 pipeline
                connection.stringCommands().set(key.getBytes(), value.getBytes());
            }
            return null;
        });

        // 结束计时
        long elapsedTime = System.currentTimeMillis() - startTime;

        log.info("使用String类型批量插入{}条数据,总耗时为: {}ms", dataCount, elapsedTime);
         使用 Lua 脚本删除匹配的键
        //String luaScript = "local keys = redis.call('keys', ARGV[1]) " +
        //        "for i, key in ipairs(keys) do " +
        //        "redis.call('del', key) " +
        //        "end " +
        //        "return #keys";
        //DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
        //Long deletedKeysCount = redisTemplate.execute(script, Collections.emptyList(), StrKey + "*");
        //
        //log.info("成功删除前缀为 {} 的所有键,共删除 {} 个键", StrKey, deletedKeysCount);

    }

性能对比
插入方式插入数据量耗时(ms)
循环插入1万条500+
批量插入1 万条150+

2.2 ZSet(有序集合)类型
  • 循环插入:
    /**
     * ZSet测试循环插入性能
     *
     * @param dataCount
     */
    public void testInsertPerformanceZSet(int dataCount) {
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < dataCount; i++) {
            stringRedisTemplate.opsForZSet().add(ZSetKey, "value" + i, i);
        }
        log.info("使用ZSet类型循环插入{}条数据,总耗时为: {} ms", dataCount, System.currentTimeMillis() - beginTime);

    }

  • 批量插入:

使用 opsForZSet().add() 的批量插入方法:

    /**
     * ZSet测试批量处理插入性能
     *
     * @param dataCount
     */
    public void testBatchInsertOptimizedZSet(int dataCount) {
        // 开始计时
        long startTime = System.currentTimeMillis();

        HashSet<ZSetOperations.TypedTuple<String>> redisBatchData = new HashSet<>();
        for (int i = 0; i < dataCount; i++) {
            redisBatchData.add(ZSetOperations.TypedTuple.of("value" + i, (double) i));
        }
        // 一次性批量插入
        stringRedisTemplate.opsForZSet().add(ZSetKey, redisBatchData);

        log.info("使用ZSet类型批量插入{}条数据,总耗时:{}ms ", dataCount, (System.currentTimeMillis() - startTime));
    }

性能对比
插入方式插入数据量耗时(ms)
循环插入1 万条660+
批量插入1 万条50+

2.3 Hash 类型
  • 循环插入:
    /**
     * Hash类型测试循环插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testInsertPerformanceHash(int dataCount) {
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < dataCount; i++) {
            stringRedisTemplate.opsForHash().put(HashKey, "key" + i, "value" + i);
        }
        log.info("使用Hash类型循环插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - beginTime);
    }
  • 批量插入:
    /**
     * Hash类型测试批量处理插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testBatchInsertOptimizedHash(int dataCount) {
        long startTime = System.currentTimeMillis();

        // 构造批量数据
        Map<String, String> hashData = IntStream.range(0, dataCount)
                .boxed()
                .collect(Collectors.toMap(i -> "key" + i, i -> "value" + i));

        // 批量插入
        stringRedisTemplate.opsForHash().putAll(HashKey, hashData);

        log.info("使用Hash类型批量插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - startTime);
    }

性能对比
插入方式插入数据量耗时(ms)
循环插入1 万条450+
批量插入1 万条15+

2.4 Set 类型
  • 循环插入:
    /**
     * Set类型测试循环插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testInsertPerformanceSet(int dataCount) {
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < dataCount; i++) {
            stringRedisTemplate.opsForSet().add("lps::test_set", "value" + i);
        }
        log.info("使用Set类型循环插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - beginTime);
    }
  • 批量插入:
    /**
     * Set类型测试批量处理插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testBatchInsertOptimizedSet(int dataCount) {
        long startTime = System.currentTimeMillis();

        // 批量插入
        stringRedisTemplate.opsForSet().add("lps::test_set", Arrays.toString(IntStream.range(0, dataCount)
                .mapToObj(i -> "value" + i).distinct().toArray()));

        log.info("使用Set类型批量插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - startTime);
    }

性能对比
插入方式插入数据量耗时(ms)
循环插入1 万条430+
批量插入1 万条2+

2.5 Geo 类型
  • 循环插入:
    /**
     * Geo类型测试循环插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testInsertPerformanceGeo(int dataCount) {
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < dataCount; i++) {
            stringRedisTemplate.opsForGeo().add("lps::test_geo", generateValidPoint(i), "location" + i);
        }
        log.info("使用Geo类型循环插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - beginTime);
    }

        /**
     * 生成合法的 Geo 数据点
     */
    private Point generateValidPoint(int index) {
        // 生成经度 [-180, 180]
        double longitude = (index % 360) - 180;

        // 生成纬度 [-85.05112878, 85.05112878]
        double latitude = ((index % 170) - 85) * 0.1;

        return new Point(longitude, latitude);
    }

  • 批量插入:
    /**
     * Geo类型测试批量处理插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testBatchInsertOptimizedGeo(int dataCount) {
        long startTime = System.currentTimeMillis();

        // 构造批量数据
        List<RedisGeoCommands.GeoLocation<String>> geoLocations = IntStream.range(0, dataCount)
                .mapToObj(i -> new RedisGeoCommands.GeoLocation<>("location" + i,
                        generateValidPoint(i)))
                .collect(Collectors.toList());

        // 批量插入
        stringRedisTemplate.opsForGeo().add("lps::test_geo", geoLocations);

        log.info("使用Geo类型批量插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - startTime);
    }

        /**
     * 生成合法的 Geo 数据点
     */
    private Point generateValidPoint(int index) {
        // 生成经度 [-180, 180]
        double longitude = (index % 360) - 180;

        // 生成纬度 [-85.05112878, 85.05112878]
        double latitude = ((index % 170) - 85) * 0.1;

        return new Point(longitude, latitude);
    }
性能对比
插入方式插入数据量耗时(ms)
循环插入1 万条496+
批量插入1 万条27+

 2.6 List 类型
  • 循环插入:
    /**
     * List类型测试循环插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testInsertPerformanceList(int dataCount) {
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < dataCount; i++) {
            stringRedisTemplate.opsForList().rightPush(ListKey, "value" + i);
        }
        log.info("使用List类型循环插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - beginTime);
    }

  • 批量插入:
    /**
     * List类型测试批量处理插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testBatchInsertOptimizedList(int dataCount) {
        long startTime = System.currentTimeMillis();

        // 构造批量数据
        List<String> values = IntStream.range(0, dataCount)
                .mapToObj(i -> "value" + i)
                .collect(Collectors.toList());

        // 批量插入
        stringRedisTemplate.opsForList().rightPushAll(ListKey, values);

        log.info("使用List类型批量插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - startTime);
    }
性能对比
插入方式插入数据量耗时(ms)
循环插入1 万条429+
批量插入1 万条8+

3. 执行图片

5.0.14.1版本 - 1w数据插入

5.0.14.1版本 - 10w数据插入

3.2.100版本 - 1w数据插入

3.2.100版本 - 10w数据插入

4. 完整类如下


@Service
@Slf4j
public class RedisServiceImpl implements RedisService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    public static final String ZSetKey = "lps::test_zset";
    public static final String StrKey = "lps::test_str::";
    public static final String ListKey = "lps::test_list";
    public static final String HashKey = "lps::test_hash";

    @Override
    public void runInsert(int number) {
        checkVersion();

        //ZSet类型
        testInsertPerformanceZSet(number);
        testBatchInsertOptimizedZSet(number);

        //String类型
        testInsertPerformanceStr(number);
        testBatchInsertOptimizedStr(number);

        //List类型
        testInsertPerformanceList(number);
        testBatchInsertOptimizedList(number);

        //Hash类型
        testInsertPerformanceHash(number);
        testBatchInsertOptimizedHash(number);

        //Set类型
        testInsertPerformanceSet(number);
        testBatchInsertOptimizedSet(number);

        //Geo类型
        testInsertPerformanceGeo(number);
        testBatchInsertOptimizedGeo(number);

    }

    public void checkVersion() {
        RedisConnection connection = Objects.requireNonNull(stringRedisTemplate.getConnectionFactory()).getConnection();
        String redisVersion = String.valueOf(Objects.requireNonNull(connection.info("server")).get("redis_version"));
        log.info("本台机器的Redis 版本为:{}", redisVersion);
    }

    /**
     * ZSet测试循环插入性能
     *
     * @param dataCount
     */
    public void testInsertPerformanceZSet(int dataCount) {
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < dataCount; i++) {
            stringRedisTemplate.opsForZSet().add(ZSetKey, "value" + i, i);
        }
        log.info("使用ZSet类型循环插入{}条数据,总耗时为: {} ms", dataCount, System.currentTimeMillis() - beginTime);

    }

    /**
     * ZSet测试批量处理插入性能
     *
     * @param dataCount
     */
    public void testBatchInsertOptimizedZSet(int dataCount) {
        // 开始计时
        long startTime = System.currentTimeMillis();

        HashSet<ZSetOperations.TypedTuple<String>> redisBatchData = new HashSet<>();
        for (int i = 0; i < dataCount; i++) {
            redisBatchData.add(ZSetOperations.TypedTuple.of("value" + i, (double) i));
        }
        // 一次性批量插入
        stringRedisTemplate.opsForZSet().add(ZSetKey, redisBatchData);

        log.info("使用ZSet类型批量插入{}条数据,总耗时:{}ms ", dataCount, (System.currentTimeMillis() - startTime));
    }

    /**
     * String类型测试循环插入性能
     *
     * @param dataCount
     */
    public void testInsertPerformanceStr(int dataCount) {
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < dataCount; i++) {
            stringRedisTemplate.opsForValue().set(StrKey + i, "value" + i);
        }
        log.info("使用String类型循环插入{}条数据,总耗时为: {} ms", dataCount, System.currentTimeMillis() - beginTime);

    }

    /**
     * String类型测试批量处理插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testBatchInsertOptimizedStr(int dataCount) {
        // 开始计时
        long startTime = System.currentTimeMillis();

        // 使用 Redis Pipeline 进行批量插入
        stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (int i = 0; i < dataCount; i++) {
                // 构造键值对
                String key = StrKey + i;
                String value = "value" + i;

                // 将命令加入 pipeline
                connection.stringCommands().set(key.getBytes(), value.getBytes());
            }
            return null;
        });

        // 结束计时
        long elapsedTime = System.currentTimeMillis() - startTime;

        log.info("使用String类型批量插入{}条数据,总耗时为: {}ms", dataCount, elapsedTime);
         使用 Lua 脚本删除匹配的键
        //String luaScript = "local keys = redis.call('keys', ARGV[1]) " +
        //        "for i, key in ipairs(keys) do " +
        //        "redis.call('del', key) " +
        //        "end " +
        //        "return #keys";
        //DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
        //Long deletedKeysCount = redisTemplate.execute(script, Collections.emptyList(), StrKey + "*");
        //
        //log.info("成功删除前缀为 {} 的所有键,共删除 {} 个键", StrKey, deletedKeysCount);

    }

    /**
     * List类型测试循环插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testInsertPerformanceList(int dataCount) {
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < dataCount; i++) {
            stringRedisTemplate.opsForList().rightPush(ListKey, "value" + i);
        }
        log.info("使用List类型循环插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - beginTime);
    }

    /**
     * List类型测试批量处理插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testBatchInsertOptimizedList(int dataCount) {
        long startTime = System.currentTimeMillis();

        // 构造批量数据
        List<String> values = IntStream.range(0, dataCount)
                .mapToObj(i -> "value" + i)
                .collect(Collectors.toList());

        // 批量插入
        stringRedisTemplate.opsForList().rightPushAll(ListKey, values);

        log.info("使用List类型批量插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - startTime);
    }

    /**
     * Hash类型测试循环插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testInsertPerformanceHash(int dataCount) {
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < dataCount; i++) {
            stringRedisTemplate.opsForHash().put(HashKey, "key" + i, "value" + i);
        }
        log.info("使用Hash类型循环插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - beginTime);
    }

    /**
     * Hash类型测试批量处理插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testBatchInsertOptimizedHash(int dataCount) {
        long startTime = System.currentTimeMillis();

        // 构造批量数据
        Map<String, String> hashData = IntStream.range(0, dataCount)
                .boxed()
                .collect(Collectors.toMap(i -> "key" + i, i -> "value" + i));

        // 批量插入
        stringRedisTemplate.opsForHash().putAll(HashKey, hashData);

        log.info("使用Hash类型批量插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - startTime);
    }

    /**
     * Set类型测试循环插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testInsertPerformanceSet(int dataCount) {
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < dataCount; i++) {
            stringRedisTemplate.opsForSet().add("lps::test_set", "value" + i);
        }
        log.info("使用Set类型循环插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - beginTime);
    }

    /**
     * Set类型测试批量处理插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testBatchInsertOptimizedSet(int dataCount) {
        long startTime = System.currentTimeMillis();

        // 批量插入
        stringRedisTemplate.opsForSet().add("lps::test_set", Arrays.toString(IntStream.range(0, dataCount)
                .mapToObj(i -> "value" + i).distinct().toArray()));

        log.info("使用Set类型批量插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - startTime);
    }

    /**
     * Geo类型测试循环插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testInsertPerformanceGeo(int dataCount) {
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < dataCount; i++) {
            stringRedisTemplate.opsForGeo().add("lps::test_geo", generateValidPoint(i), "location" + i);
        }
        log.info("使用Geo类型循环插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - beginTime);
    }

    /**
     * Geo类型测试批量处理插入性能
     *
     * @param dataCount 插入的数据总量
     */
    public void testBatchInsertOptimizedGeo(int dataCount) {
        long startTime = System.currentTimeMillis();

        // 构造批量数据
        List<RedisGeoCommands.GeoLocation<String>> geoLocations = IntStream.range(0, dataCount)
                .mapToObj(i -> new RedisGeoCommands.GeoLocation<>("location" + i,
                        generateValidPoint(i)))
                .collect(Collectors.toList());

        // 批量插入
        stringRedisTemplate.opsForGeo().add("lps::test_geo", geoLocations);

        log.info("使用Geo类型批量插入{}条数据,总耗时为: {}ms", dataCount, System.currentTimeMillis() - startTime);
    }

    /**
     * 生成合法的 Geo 数据点
     */
    private Point generateValidPoint(int index) {
        // 生成经度 [-180, 180]
        double longitude = (index % 360) - 180;

        // 生成纬度 [-85.05112878, 85.05112878]
        double latitude = ((index % 170) - 85) * 0.1;

        return new Point(longitude, latitude);
    }
}

5. 总结与优化建议

  • 批量插入性能远远高于循环插入,尤其是在数据量较大时,差距更为明显。
  • 使用 Redis Pipeline 或批量操作接口是提高 Redis 性能的有效方法。
  • 在批量操作时,尽量将数据准备在内存中再一次性提交到 Redis,减少网络开销。
  • Geo 类型插入需注意经纬度合法范围,避免报错。

通过这些优化方法,Redis 的插入性能可以得到显著提升,在高并发场景下尤为重要。

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

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

相关文章

uniapp页面样式和布局和nvue教程详解

uniapp页面样式和布局和nvue教程 尺寸单位 uni-app 支持的通用 css 单位包括 px、rpx px 即屏幕像素。rpx 即响应式px&#xff0c;一种根据屏幕宽度自适应的动态单位。以750宽的屏幕为基准&#xff0c;750rpx恰好为屏幕宽度。屏幕变宽&#xff0c;rpx 实际显示效果会等比放大…

Spring AI 框架使用的核心概念

一、模型&#xff08;Model&#xff09; AI 模型是旨在处理和生成信息的算法&#xff0c;通常模仿人类的认知功能。通过从大型数据集中学习模式和见解&#xff0c;这些模型可以做出预测、文本、图像或其他输出&#xff0c;从而增强各个行业的各种应用。 AI 模型有很多种&…

(Keil)MDK-ARM各种优化选项详细说明、实际应用及拓展内容

参考 MDK-ARM各种优化选项详细说明、实际应用及拓展内容 本文围绕MDK-ARM优化选项,以及相关拓展知识(微库、实际应用、调试)进行讲述,希望对你今后开发项目有所帮助。 1 总述 我们所指的优化,主要两方面: 1.代码大小(Size) 2.代码性能(运行时间) 在MDK-ARM中,优…

python成绩分级 2024年6月python二级真题 青少年编程电子学会编程等级考试python二级真题解析

目录 python成绩分级 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python成绩分级 2024年6月 python编程等级考试二级编程题 一、题目要求 …

苹果Siri将搭载大型语言模型,近屿智能抢占AIGC大模型人才培养高地

据媒体报道&#xff0c;苹果公司正在研发一款全新升级、更加智能且对话能力显著提升的Siri&#xff0c;意在超越OpenAI的ChatGPT及其他语音服务。 报道指出&#xff0c;新一代Siri将搭载更为先进的大型语言模型&#xff08;LLM&#xff09;&#xff0c;苹果期望其能够进行连续…

Ubuntu,openEuler,MySql安装

文章目录 Ubuntu什么是Ubuntu概述Ubuntu版本简介桌面版服务器版 部署系统新建虚拟机安装系统部署后的设置设置root密码关闭防火墙启用允许root进行ssh安装所需软件制作快照 网络配置Netplan概述配置详解配置文件DHCP静态IP设置 软件安装方法apt安装软件作用常用命令配置apt源 d…

【C++动态规划】1411. 给 N x 3 网格图涂色的方案数|1844

本文涉及知识点 C动态规划 LeetCode1411. 给 N x 3 网格图涂色的方案数 提示 你有一个 n x 3 的网格图 grid &#xff0c;你需要用 红&#xff0c;黄&#xff0c;绿 三种颜色之一给每一个格子上色&#xff0c;且确保相邻格子颜色不同&#xff08;也就是有相同水平边或者垂直…

小鹏汽车智慧材料数据库系统项目总成数据同步

1、定时任务处理 2、提供了接口 小鹏方面提供的推送的数据表结构&#xff1a; 这几个表总数为100多万&#xff0c;经过条件筛选过滤后大概2万多条数据 小鹏的人给的示例图&#xff1a; 界面&#xff1a; SQL: -- 查询车型 select bmm.md_material_id, bmm.material_num, bm…

【解决方案】VITE 忽略指定路径的资源

前言 问题起因是因为项目需要引入服务器端的网络图片 而在编写配置时发现&#xff0c;Vite并不支持排除指定前缀的资源 唯一可以排外的只有 Rollup 的 external 选项可以排除外部依赖&#xff0c;但他只能排除外部依赖&#xff0c;不支持指定路径资源或指定前缀的资源&#…

自然语言处理: RAG优化之Embedding模型选型重要依据:mteb/leaderboard榜

本人项目地址大全&#xff1a;Victor94-king/NLP__ManVictor: CSDN of ManVictor git地址&#xff1a;https://github.com/opendatalab/MinerU 写在前面: 笔者更新不易&#xff0c;希望走过路过点个关注和赞&#xff0c;笔芯!!! 写在前面: 笔者更新不易&#xff0c;希望走过路…

疑难Tips:NextCloud域名访问登录时卡住,显示违反内容安全策略

[ 知识是人生的灯塔,只有不断学习,才能照亮前行的道路 ] 1使用域名访问Nextcloud用户登录时卡住,显示违反内容安全策略 我使用官方Docker镜像来部署NextCloud 28.0.5,并通过Openresty反向代理Nextcloud,但是在安装后无法稳定工作,每次登录后,页面会卡死在登录界面,无法…

SpringBoot 集成 html2Pdf

一、概述&#xff1a; 1. springboot如何生成pdf&#xff0c;接口可以预览可以下载 2. vue下载通过bold如何下载 3. 一些细节&#xff1a;页脚、页眉、水印、每一页得样式添加 二、直接上代码【主要是一个记录下次开发更快】 模板位置 1. 导入pom包 <dependency><g…

java实现小程序接口返回Base64图片

文章目录 引言I java 接口返回Base64图片接口设计获取验证码图片-base64字符串获取验证码图片-二进制流arraybufferII 小程序端代码过期代码: 显示文件流图片(arraybuffer)知识扩展:微信小程序下载后端返回的文件流引言 场景: 图形验证码 背景: 接口返回arraybuffer的格式…

Pytorch自定义算子反向传播

文章目录 自定义一个线性函数算子如何实现反向传播 有关 自定义算子的实现前面已经提到&#xff0c;可以参考。本文讲述自定义算子如何前向推理反向传播进行模型训练。 自定义一个线性函数算子 线性函数 Y X W T B Y XW^T B YXWTB 定义输入M 个X变量&#xff0c;输出N个…

ajax (一)

什么是 AJAX [ˈeɪdʒks] &#xff1f; 概念&#xff1a;AJAX是浏览器与服务器进行 数据通信 的技术&#xff0c;动态数据交互 怎么用AJAX? 1. 先使用 axios [k‘sio ʊ s] 库&#xff0c; 与服务器进行 数据通信 ⚫ 基于 XMLHttpRequest 封装、代码简单、月下载量在 1…

URL在线编码解码- 加菲工具

URL在线编码解码 打开网站 加菲工具 选择“URL编码解码” 输入需要编码/解码的内容&#xff0c;点击“编码”/“解码”按钮 编码&#xff1a; 解码&#xff1a; 复制已经编码/解码后的内容。

魔众题库系统 v10.0.0 客服条、题目导入、考试导航、日志一大批更新

魔众题库系统基于PHP开发&#xff0c;可以用于题库管理和试卷生成软件&#xff0c;拥有极简界面和强大的功能&#xff0c;用户遍及全国各行各业。 魔众题库系统发布v10.0.0版本&#xff0c;新功能和Bug修复累计30项&#xff0c;客服条、题目导入、考试导航、日志一大批更新。 …

深入解析 EasyExcel 组件原理与应用

✨深入解析 EasyExcel 组件原理与应用✨ 官方&#xff1a;EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel 官网 在日常的 Java 开发工作中&#xff0c;处理 Excel 文件的导入导出是极为常见的需求。 今天&#xff0c;咱们就一起来深入了解一款非常实用的操作 Exce…

本地部署 MaskGCT

本地部署 MaskGCT 0. 更新系统和安装依赖项1. 克隆代码2. 创建虚拟环境3. 安装依赖模块4. 运行 MaskGCT5. 访问 MaskGCT 0. 更新系统和安装依赖项 sudo apt update sudo apt install espeak-ng1. 克隆代码 git clone https://github.com/engchina/learn-maskgct.git; cd lear…

线程控制方法之wait和sleep的区别

线程控制方法之wait和sleep的区别 wait()和sleep()都是Java线程控制方法&#xff0c;但存在明显区别&#xff1a; 所属与调用&#xff1a;wait()属Object类&#xff0c;需synchronized调用&#xff1b;sleep()属Thread类&#xff0c;可随意调用。锁处理&#xff1a;wait()释放…