伙伴匹配推荐接口的优化策略【优先队列+多线程分批处理,java实现】

news2025/4/22 16:02:59

文章目录

  • 接口背景
  • 接口问题说明
  • 优化策略
  • 用户匹配度计算
  • 接口改进与测试
    • 说明
    • 改进前
    • 改进一(使用优先队列存储编辑距离较小的n个元素)
    • 改进二(使用优先队列存储编辑距离较小的n个元素+数据分批查询、分批处理)
    • 改进三(使用优先队列存储编辑距离较小的n个元素+数据多线程分批查询、分批处理)
  • 总结

接口背景

该接口来源于鱼皮大佬的星球项目——伙伴匹配系统,接口的作用是:根据当前登录用户的标签,为其匹配标签接近的用户,快速帮其找到志同道合的人

接口问题说明

当用户量较大的时候(如到达百万级别),伙伴匹配速度较慢,且占用的内存非常大,系统的并发量较低,希望可以通过一些策略来降低内存占用并加快匹配速度。

在这里插入图片描述

优化策略

鱼皮大佬在项目讲解的时候,已经提出了一些优化策略,如:

  1. 关掉数据库的查询日志,可以快30秒左右(不同机器提高时间不同,总之提升巨大)
  2. 查询数据的时候,只查询需要使用的字段,不要直接将所有字段都查询出来
  3. 查询数据的时候过滤掉“标签”字段为空的数据
  4. 内存预热,在系统用户量少的时候(如凌晨两点)提前缓存好用户的伙伴匹配数据(注意,如果缓存占用内存过多,可以只针对一些关键用户做内存预热,如活跃度高的用户、vip用户)

用户匹配度计算

该系统主要根据用户的标签来判断用户是否匹配,如用户1的标签是[“java”,“python”,“前端”]、用户2的标签是[“java”,“python”,“前端”],则两个用户是完全匹配的。

接口使用编辑距离来计算两个标签的匹配度,若两者的编辑距离越小,则匹配度越高。编辑距离是针对两个字符串的差异程度的度量,即至少需要多少次操作(操作方式有:新增、删除、修改一个字符)才能将一个字符串转换成另一个字符串。本文接口对编辑距离的应用则是将一个标签集合转换成另一个标签集合至少需要多少次操作,可以查看如下示例:

输入: 标签集合1= ["java","python","前端","matlab"], 标签集合2= ["java","python","后端"]
输出: 2
解释: 
["java","python","前端","matlab"]-> ["java","python","后端","matlab"] ('前端' 替换为 '后端')
["java","python","后端","matlab"]-> ["java","python","后端"] ('matlab' 删除)

接口改进与测试

说明

以下接口测试使用相同的数据,目标是为当前登录用户推荐十个匹配度最高(编辑距离最小)的用户。

改进前

从下图可以看出,接口调用时间为5秒左右,图中对象的占用内存达到 200MB。

在这里插入图片描述

改进一(使用优先队列存储编辑距离较小的n个元素)

堆是一种实用的数据结构,常用来求解 Top K 问题,比如 如何快速获取点赞数量最多的十篇文章,本文接口目标是取出编辑距离最小(即匹配度最高)的十个用户。

实现思路:维护一个节点数量为10的大顶堆,节点的值为编辑距离,堆是一颗完全二叉树,堆中节点的值都大于等于其子节点的值,则堆顶元素的编辑距离最大。想要维护十个编辑距离最小的元素,只需要在遍历元素的时候,判断新元素的编辑距离是否小于堆顶元素的编辑距离,若小于,则踢出堆顶元素,加入新元素即可。在java中,可以使用优先队列 PriorityQueue 来实现大顶堆的操作。

/**
 * 获取前num个最匹配的用户(使用优先队列优化内存占用)
 *
 * @param num
 * @param loginUser
 * @return
 */
public List<User> matchUsers1(int num, User loginUser) {
    long start = System.currentTimeMillis();

    // 一、获取当前登录用户的标签信息
    Gson gson = new Gson();
    // 将字符串json反序列回 集合
    List<String> tagList = gson.fromJson(loginUser.getTags(), new TypeToken<List<String>>() {
    }.getType());

    // 二、查询数据库中的所有用户数据(只查询两个字段,且只查询tags不为空的数据)
    QueryWrapper<User> queryWrapper = new QueryWrapper<User>().select("id", "tags").isNotNull("tags");
    List<User> userList = this.list(queryWrapper);

    // 三、创建优先队列,用来存储前num个距离最小(距离越小,匹配度越高)的用户
    // 创建比较器(按照编辑距离降序排序)
    Comparator<Pair<User, Long>> comparator = new Comparator<Pair<User, Long>>() {
        public int compare(Pair<User, Long> o1, Pair<User, Long> o2) {
            return -Long.compare(o1.getValue(), o2.getValue());
        }
    };
    // 堆的初始容量
    int initialCapacity = num;
    // 维护一个大顶堆,堆的顶部元素最大,在迭代的时候,如果新的距离比堆顶元素更小,则将堆顶元素踢出,添加新的元素
    PriorityQueue<Pair<User, Long>> priorityQueue = new PriorityQueue<Pair<User, Long>>(initialCapacity, comparator);

    // 四、先将前面num个元素添加到优先队列中
    int userListSize = userList.size();
    // 计算提前插入量(用户数量还不一定有查询数量多)
    int advanceInsertAmount = Math.min(initialCapacity, userListSize);
    // 已经插入优先队列的元素数量
    int insertNum = 0;
    // 记录当前所迭代到用户的索引
    int index = 0;
    while (insertNum < advanceInsertAmount && index < userListSize - 1) {
        // index++,是先get,之后才执行 +1 逻辑
        User user = userList.get(index++);
        String userTags = user.getTags();
        // 排除无标签的用户或者自己
        if (StringUtils.isBlank(userTags) || user.getId() == loginUser.getId()) {
            continue;
        } else {
            List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {
            }.getType());
            // 计算编辑距离
            long distance = AlgorithmUtils.minDistance(tagList, userTagList);
            // 添加元素到堆中
            priorityQueue.add(new Pair<>(user, distance));
            insertNum++;
        }
    }

    // 五、依次计算剩余所有用户的编辑距离,并更新优先队列的元素
    for (int i = index; i < userListSize; i++) {
        User user = userList.get(i);
        String userTags = user.getTags();
        // 排除无标签的用户或者自己
        if (StringUtils.isBlank(userTags) || user.getId() == loginUser.getId()) {
            continue;
        }
        List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {
        }.getType());
        // 计算编辑距离
        long distance = AlgorithmUtils.minDistance(tagList, userTagList);
        // 获取堆顶元素的编辑距离
        Long biggestDistance = priorityQueue.peek().getValue();
        if (distance < biggestDistance) {
            // 删除堆顶元素(删除距离最大的元素)
            priorityQueue.poll();
            // 添加距离更小的元素
            priorityQueue.add(new Pair<>(user, distance));
        }
    }

    // 六、获取用户的详细信息
    List<Long> userIdList = priorityQueue.stream().map(pair -> pair.getKey().getId()).collect(Collectors.toList());
    List<User> finalUserList = this.list(new QueryWrapper<User>().in("id", userIdList))
            .stream()
            // 用户数据脱敏
            .map(user -> getSafetyUser(user)).collect(Collectors.toList());

    long end = System.currentTimeMillis();
    System.out.println("匹配时间:" + (end - start) + "ms");
    System.out.println("队列长度:" + priorityQueue.size());
//        System.out.println("使用内存统计-----------------------------------------------------------------------------------------");
//        System.out.println("priorityQueue内存大小" + RamUsageEstimator.sizeOf(priorityQueue));
    System.out.println("priorityQueue内存大小" + RamUsageEstimator.humanSizeOf(priorityQueue));

    for (Pair<User, Long> userPair : priorityQueue) {
        System.out.println("用户id:" + userPair.getKey().getId() + ",距离:" + userPair.getValue());
    }
    System.out.println();

    return finalUserList;
}

在这里插入图片描述
如上图所示,priorityQueue的内存占用是25MB左右,远远小于未改进前的200MB。但是不要被迷惑了,上述代码一开始还是使用userList来接收所有用户数据,因此峰值内存并没有减少。

改进二(使用优先队列存储编辑距离较小的n个元素+数据分批查询、分批处理)

既然一开始使用userList来接收所有用户数据会占用不少内存,那是否可以对此进行优化呢?答案显然是可以的,那就是对数据进行分批查询(分页查询)即可,查询一批就处理一批,处理完直接将数据丢掉即可,具体操作可以查看下面的代码。

/**
 * 获取前num个最匹配的用户(使用优先队列优化内存占用+数据分批查询、分批处理)
 *
 * @param num
 * @param loginUser
 * @return
 */
public List<User> matchUsers2(int num, User loginUser) {
    long start = System.currentTimeMillis();
    // 将数据分批,每批所要处理的数据量
    int batchSize = 200000;
    int current = 1;

    // 一、获取当前登录用户的标签信息
    Gson gson = new Gson();
    // 将字符串json反序列回 集合
    List<String> tagList = gson.fromJson(loginUser.getTags(), new TypeToken<List<String>>() {
    }.getType());

    // 二、查询数据库中的所有用户数据(只查询两个字段,且只查询tags不为空的数据)
    QueryWrapper<User> queryWrapper = new QueryWrapper<User>().select("id", "tags").isNotNull("tags");

    // 三、创建优先队列,用来存储前num个距离最小(距离越小,匹配度越高)的用户
    // 创建比较器(按照编辑距离降序排序)
    Comparator<Pair<User, Long>> comparator = new Comparator<Pair<User, Long>>() {
        public int compare(Pair<User, Long> o1, Pair<User, Long> o2) {
            return -Long.compare(o1.getValue(), o2.getValue());
        }
    };
    // 堆的初始容量
    int initialCapacity = num;
    // 维护一个大顶堆,堆的顶部元素最大,在迭代的时候,如果新的距离比堆顶元素更小,则将堆顶元素踢出,添加新的元素
    PriorityQueue<Pair<User, Long>> priorityQueue = new PriorityQueue<Pair<User, Long>>(initialCapacity, comparator);

    while (true) {
        System.out.println("current:" + current);
        Page<User> userPage = this.page(new Page<>(current, batchSize), queryWrapper);
        List<User> userList = userPage.getRecords();
        System.out.println("userList内存大小" + RamUsageEstimator.humanSizeOf(userList));
        if (userList.size() == 0) {
            break;
        }
        System.out.println("当前用户id:" + userList.get(0).getId());

        // 四、先将前面num个元素添加到优先队列中
        // 记录当前所迭代到用户的索引
        int index = 0;
        if (current == 1) {
            int userListSize = userList.size();
            // 计算提前插入量(用户数量还不一定有查询数量多)
            int advanceInsertAmount = Math.min(initialCapacity, userListSize);
            // 已经插入优先队列的元素数量
            int insertNum = 0;
            while (insertNum < advanceInsertAmount && index < userListSize - 1) {
                // index++,是先get,之后才执行 +1 逻辑
                User user = userList.get(index++);
                String userTags = user.getTags();
                // 排除无标签的用户或者自己
                if (StringUtils.isBlank(userTags) || user.getId() == loginUser.getId()) {
                    continue;
                } else {
                    List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {
                    }.getType());
                    // 计算编辑距离
                    long distance = AlgorithmUtils.minDistance(tagList, userTagList);
                    // 添加元素到堆中
                    priorityQueue.add(new Pair<>(user, distance));
                    insertNum++;
                }
            }
        }

        // 五、依次计算剩余所有用户的编辑距离,并更新优先队列的元素
        for (int i = index; i < userList.size(); i++) {
            User user = userList.get(i);
            String userTags = user.getTags();
            // 排除无标签的用户或者自己
            if (StringUtils.isBlank(userTags) || user.getId() == loginUser.getId()) {
                continue;
            }
            List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {
            }.getType());
            // 计算编辑距离
            long distance = AlgorithmUtils.minDistance(tagList, userTagList);
            // 获取堆顶元素的编辑距离
            Long biggestDistance = priorityQueue.peek().getValue();
            if (distance < biggestDistance) {
                // 删除堆顶元素(删除距离最大的元素)
                priorityQueue.poll();
                // 添加距离更小的元素
                priorityQueue.add(new Pair<>(user, distance));
            }
        }
        current++;
    }

    // 六、获取用户的详细信息
    List<Long> userIdList = priorityQueue.stream().map(pair -> pair.getKey().getId()).collect(Collectors.toList());
    List<User> finalUserList = this.list(new QueryWrapper<User>().in("id", userIdList))
            .stream()
            // 用户数据脱敏
            .map(user -> getSafetyUser(user)).collect(Collectors.toList());

    long end = System.currentTimeMillis();
    System.out.println("匹配时间:" + (end - start) + "ms");
    System.out.println("队列长度:" + priorityQueue.size());
//        System.out.println("使用内存统计-----------------------------------------------------------------------------------------");
//        System.out.println("priorityQueue内存大小" + RamUsageEstimator.sizeOf(priorityQueue));
    System.out.println("priorityQueue内存大小" + RamUsageEstimator.humanSizeOf(priorityQueue));

    for (Pair<User, Long> userPair : priorityQueue) {
        System.out.println("用户id:" + userPair.getKey().getId() + ",距离:" + userPair.getValue());
    }
    System.out.println();

    return finalUserList;
}

下图为分批处理过程中,userList的内存占用,这样峰值内存占用就减下来了,但是接口调用时间却翻了一倍,妥妥的“时间换空间”了。

在这里插入图片描述

改进三(使用优先队列存储编辑距离较小的n个元素+数据多线程分批查询、分批处理)

既然数据都分批处理了,那为何不想办法让多个线程同时处理呢,这样接口调用时间就可以减下来了。要注意的是:PriorityQueue是线程不安全的,在使用多线程的时候,应该使用其好兄弟PriorityBlockingQueue

/**
* 创建线程池
*/
private ExecutorService executor = new ThreadPoolExecutor(5, 1000, 10000, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10000));

/**
 * 获取前num个最匹配的用户(使用优先队列优化内存占用+数据分批查询、分批处理)
 *
 * @param num
 * @param loginUser
 * @return
 */
public List<User> matchUsers3(int num, User loginUser) {
    long start = System.currentTimeMillis();
    // 将数据分批,每批所要处理的数据量
    int batchSize = 200000;

    // 一、获取当前登录用户的标签信息
    Gson gson = new Gson();
    // 将字符串json反序列回 集合
    List<String> tagList = gson.fromJson(loginUser.getTags(), new TypeToken<List<String>>() {
    }.getType());

    // 二、查询数据库中的所有用户数据(只查询两个字段,且只查询tags不为空的数据)
    long totalUserNum = baseMapper.selectCount(new QueryWrapper<>());
    QueryWrapper<User> queryWrapper = new QueryWrapper<User>().select("id", "tags").isNotNull("tags");

    // 三、创建优先队列,用来存储前num个距离最小(距离越小,匹配度越高)的用户
    // 创建比较器(按照编辑距离降序排序)
    Comparator<Pair<User, Long>> comparator = new Comparator<Pair<User, Long>>() {
        public int compare(Pair<User, Long> o1, Pair<User, Long> o2) {
            return -Long.compare(o1.getValue(), o2.getValue());
        }
    };
    // 堆的初始容量
    int initialCapacity = num;
    // 维护一个大顶堆,堆的顶部元素最大,在迭代的时候,如果新的距离比堆顶元素更小,则将堆顶元素踢出,添加新的元素
    PriorityBlockingQueue<Pair<User, Long>> priorityQueue = new PriorityBlockingQueue<Pair<User, Long>>(initialCapacity, comparator);

    // 计算分批数
    int batchNum = totalUserNum / batchSize + (totalUserNum % batchSize) > 0 ? 1 : 0;
    List<CompletableFuture<Void>> futureList = new ArrayList<>();
    for (int current = 1; current <= batchNum; current++) {
        // 异步执行
        int finalCurrent = current;
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            Page<User> userPage = this.page(new Page<>(finalCurrent, batchSize), queryWrapper);
            List<User> userList = userPage.getRecords();

            // 四、先将前面num个元素添加到优先队列中
            // 记录当前所迭代到用户的索引
            int index = 0;
            if (finalCurrent == 1) {
                int userListSize = userList.size();
                // 计算提前插入量(用户数量还不一定有查询数量多)
                int advanceInsertAmount = Math.min(initialCapacity, userListSize);
                // 已经插入优先队列的元素数量
                int insertNum = 0;
                while (insertNum < advanceInsertAmount && index < userListSize - 1) {
                    // index++,是先get,之后才执行 +1 逻辑
                    User user = userList.get(index++);
                    String userTags = user.getTags();
                    // 排除无标签的用户或者自己
                    if (StringUtils.isBlank(userTags) || user.getId() == loginUser.getId()) {
                        continue;
                    } else {
                        List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {
                        }.getType());
                        // 计算编辑距离
                        long distance = AlgorithmUtils.minDistance(tagList, userTagList);
                        // 添加元素到堆中
                        priorityQueue.add(new Pair<>(user, distance));
                        insertNum++;
                    }
                }
            }

            // 五、依次计算剩余所有用户的编辑距离,并更新优先队列的元素
            for (int i = index; i < userList.size(); i++) {
                User user = userList.get(i);
                String userTags = user.getTags();
                // 排除无标签的用户或者自己
                if (StringUtils.isBlank(userTags) || user.getId() == loginUser.getId()) {
                    continue;
                }
                List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {
                }.getType());
                // 计算编辑距离
                long distance = AlgorithmUtils.minDistance(tagList, userTagList);
                // 获取堆顶元素的编辑距离
                Long biggestDistance = priorityQueue.peek().getValue();
                if (distance < biggestDistance) {
                    // 删除堆顶元素(删除距离最大的元素)
                    priorityQueue.poll();
                    // 添加距离更小的元素
                    priorityQueue.add(new Pair<>(user, distance));
                }
            }
        }, executor);
        futureList.add(future);
    }
    // 阻塞,等待所有线程执行完成
    CompletableFuture.allOf(futureList.toArray(new CompletableFuture[]{})).join();

    // 六、获取用户的详细信息
    List<Long> userIdList = priorityQueue.stream().map(pair -> pair.getKey().getId()).collect(Collectors.toList());
    List<User> finalUserList = this.list(new QueryWrapper<User>().in("id", userIdList))
            .stream()
            // 用户数据脱敏
            .map(user -> getSafetyUser(user)).collect(Collectors.toList());

    long end = System.currentTimeMillis();
    System.out.println("匹配时间:" + (end - start) + "ms");
    System.out.println("队列长度:" + priorityQueue.size());
//        System.out.println("使用内存统计-----------------------------------------------------------------------------------------");
//        System.out.println("priorityQueue内存大小" + RamUsageEstimator.sizeOf(priorityQueue));
    System.out.println("priorityQueue内存大小" + RamUsageEstimator.humanSizeOf(priorityQueue));

    for (Pair<User, Long> userPair : priorityQueue) {
        System.out.println("用户id:" + userPair.getKey().getId() + ",距离:" + userPair.getValue());
    }
    System.out.println();

    return finalUserList;
}

下图为应用上述改进后的测试结果,可以看到时间被打下来了。

在这里插入图片描述

总结

程序是可以不断优化的,希望大家可以多从不同的角度来思考如何改进自己对问题的解决方式。

作者的知识浅薄,如果有大佬有更好的改进思路,求不吝赐教。

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

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

相关文章

算法修炼之练气篇——练气十九层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

互联网本来很简单,但为啥人们看的那么复杂

昨天有朋友问我互联网&#xff0c;说互联网怎么怎么创新。 我说你看到的都是像。佛说佛有十万身。这都是像&#xff0c;不是相。 &#xff08;1&#xff09; 500多年前&#xff0c;意大利美第奇家族为了给教皇运送全欧洲信民们的捐赠&#xff0c;所以建立了一张天网一张地网。天…

VMware 产品下载汇总 2023 持续更新中

本站 VMware 产品下载汇总&#xff1a;vSphere、NSX、Tanzu、Aria、Cloud… 请访问原文链接&#xff1a;https://sysin.org/blog/vmware/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 本站提供的 VMware 软件全部为 “试用版…

ENVI无缝镶嵌Seamless Mosaic工具镶嵌、拼接栅格遥感影像(所有图像需要含有地理信息)

本文介绍基于ENVI软件&#xff0c;利用“Seamless Mosaic”工具实现栅格遥感影像无缝镶嵌的操作。 在ENVI软件中通过“Pixel Based Mosaicking”工具实现栅格遥感影像的镶嵌的方法。这一工具虽然可以对不含有地理参考信息的遥感影像进行镶嵌&#xff0c;但是其镶嵌的整体效果并…

数据分析利器之python、IT应用架构规划详解(195页)、600多个人工智能AI工具汇总、营销革命5.0…| 本周精华...

▲点击上方卡片关注我&#xff0c;回复“8”&#xff0c;加入数据分析领地&#xff0c;一起学习数据分析&#xff0c;持续更新数据分析学习路径相关资料~&#xff08;精彩数据观点、学习资料、数据课程分享、读书会、分享会等你一起来乘风破浪~&#xff09;回复“小飞象”&…

Halcon 阈值分割(全局阈值threshold、binary_threshold、动态阈值 dyn_threshold)、直方图

文章目录 1 图像直方图2 全局阈值 threshold()3 binary_threshold()4 动态阈值mean_image() + dyn_threshold()5 代码和原图1 图像直方图 图像直方图(Image Histogram)是用以表示数字图像中亮度分布的直方图,标绘了图像中每个亮度值的像素个数。 这种直方图中,横坐标的左…

后端返回文件流时,前端如何处理并成功下载流文件以及解决下载后打开显示不支持此文件格式

一、文件和流的关系 文件&#xff08;File&#xff09;和流(Stream)是既有区别又有联系的两个概念。 文件 是计算机管理数据的基本单位&#xff0c;同时也是应用程序保存和读取数据的一个重要场所。 存储介质&#xff1a;文件是指在各种存储介质上&#xff08;如硬盘、可…

WPF 使用 MaterialDesignThemes 项目Demo

前言&#xff1a; 最近在学B站的WPF项目实战合集(2022终结版)&#xff0c;但是到22P时候发现UI框架 MaterialDesignThemes的Github上面的程序没办法正常运行&#xff0c;最后折腾了好久终于解决。 github地址 gitcode镜像地址 下载成功后 下载成功后是如下效果 打开这个文…

audioop.rms函数解读和代码例子

该audioop模块包含对声音片段的一些有用操作。它对由8,16或32位宽的有符号整数样本组成的声音片段进行操作&#xff0c;并以Python字符串存储。这与al和sunaudiodev模块使用的格式相同。所有标量项都是整数&#xff0c;除非另有规定。 audioop.rms 即 sqrt(sum(S_i^2)/n) 这个公…

Linux运维常用sed命令使用

sed 是一种流式文本编辑器&#xff0c;常用于文本替换、文本过滤、行选择等操作。 常见的 sed 使用方法 1、替换文本中的字符串 使用 sed 可以在文本中替换指定的字符串。例如&#xff0c;将文本中所有的 old_text 替换为 new_text&#xff0c;可以执行以下命令&#xff1a; …

面向国际市场:利用FaceBook实现外贸贸易突破

在全球化的商业环境下&#xff0c;利用社交媒体平台如FaceBook来推动外贸贸易已经成为许多企业的关注焦点。FaceBook作为全球最大的社交媒体平台之一&#xff0c;为企业提供了众多机会和工具&#xff0c;以扩大市场触达、建立品牌形象和跨文化沟通。 本文将介绍一些简单却有效…

计算机组成原理---第五章 中央处理器习题详解版

&#xff08;一&#xff09;课内习题 &#xff08;二&#xff09;课后习题 1.请在括号内填入适当答案。在CPU中&#xff1a; (1)保存当前正在执行的指令的寄存器是( IR ); (2)保存当前正在执行的指令地址的寄存器是( AR ) (3)算术逻辑运算结果通常放在( DR )和( 通用寄存器…

【openGauss实战13】闪回技术

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

SolidWorks创建自定义焊件轮廓的方法

在一些特定的设计情景下&#xff0c;一般的国标焊件库、ISO焊件库等可能满足不了我们的设计使用需求&#xff0c;这时候就需要我么你自己创建一个焊件轮廓&#xff0c;从而应用到我们的设计中。 创建新焊件轮廓的方法如下&#xff1a; 1.打开SolidWorks&#xff0c;创建一个新…

记录--9个封装Vue组件的小技巧

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 组件是前端框架的基本构建块。把它们设计得更好会使我们的应用程序更容易改变和理解。在这节课中&#xff0c;分享一下在过去几年中工作中学到的 9 个技巧。 1. 你可能不需要创建一个组件 在创建一个组…

eBpf在Android上的集成和调试

eBPF&#xff08;Extended Berkeley Packet Filter &#xff09;是一种新兴的linux内核功能扩展技术&#xff0c;可以无需修改内核代码&#xff0c;在保证安全的前提下&#xff0c;灵活的动态加载程序&#xff0c;实现对内核功能的扩展。 Android平台上也引入了对eBpf技术的支持…

Python—实现本地音乐播放器(添加/播放/暂停/下一首/上一首/音量/打开超链接)

文章目录 1.样例2.分析2.1播放器界面2.2功能2.2.1添加音乐&#xff0c;选择文件夹&#xff0c;显示文件夹里.Mp3文件2.2.2播放音乐,开始播放第一首音乐,按钮由"播放"变为"暂停",点击"暂停",变为"播放",播放显示Playing...2.2.3下一首,…

【ElasticSearch】分词器(ElasticSearchIK分词器)

1. 分词器介绍 •IKAnalyzer 是一个开源的&#xff0c;基于java语言开发的轻量级的中文分词工具包•是一个基于Maven构建的项目•具有60万字/秒的高速处理能力•支持用户词典扩展定义 2. ik 分词器安装 IK 分词器安装 3. 分词器的使用 IK分词器有两种分词模式&#xff1a;ik…

港科夜闻|香港科大与香港科大(广州)管理层联席会议顺利召开

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大与香港科大(广州)管理层联席会议顺利召开。这是自内地和香港全面恢复通关以来&#xff0c;两校的高级管理团队首次举行线下的联席会议&#xff0c;面对面交流、讨论有关两校协同发展的重要议题。两校持续深入推进…

【零基础学web前端】走进CSS的大门,CSS引入方式,CSS基础选择器,CSS复合选择器

前言: 大家好,我是良辰丫,前面我们已经学了html的相关知识,今天我们一起去探索前端网页的css,那么css到底是什么呢?我们慢慢往下看.&#x1f49e;&#x1f49e; &#x1f9d1;个人主页&#xff1a;良辰针不戳 &#x1f4d6;所属专栏&#xff1a;零基础学web前端 &#x1f34e;…