Java多线程实战-实现多线程文件下载,支持断点续传、日志记录等功能

news2024/11/17 11:37:52

🏷️个人主页:牵着猫散步的鼠鼠 

🏷️系列专栏:Java全栈-专栏

🏷️个人学习笔记,若有缺误,欢迎评论区指正 

目录

前言

1 基础知识回顾

1.1 线程的创建和启动

1.2 线程池的使用

2.运行环境说明

3.核心模块实现

3.1下载线程的实现

3.2日志线程的实现

3.3相关工具类的实现

3.4核心业务实现

4.功能测试

总结


前言

在当今快节奏的数字时代,大文件的下载已经成为我们日常生活中不可或缺的一部分。然而,传统的单线程下载器在面临大文件时往往显得力不从心,下载速度缓慢,用户体验不佳。

老读者应该知道,我最近在研究Java多线程并发编程这一块的内容,故想要编写一个多线程下载工具,一是为了知识的落地实践,二是可以将这个工具运用到平时下载大文件的地方。

1 基础知识回顾

为了照顾一些新来的小伙伴,我这里简单讲解一下在Java中一些常用的多线程实现

1.1 线程的创建和启动

在Java中,线程可以通过以下几种方式创建和启动一个新的线程:

继承Thread类:自定义一个类,继承自Thread类,并重写run()方法。

创建线程对象并调用start()方法启动线程。

public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行的代码
    }
}


实现Runnable接口:自定义一个类,实现Runnable接口,并重写run()方法。

创建Runnable对象,并将其传递给Thread对象,然后调用start()方法。

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行的代码
    }
}


使用ExecutorService:这是一个更高级的方式,用于管理线程池。本次的多线程下载器主要用的就是这种方式实现

ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(new MyRunnable());

1.2 线程池的使用

线程池是一种管理线程的更高效的方式,可以避免频繁创建和销毁线程的开销。Java中使用ExecutorService接口来管理线程池。

固定大小的线程池:

创建一个固定大小的线程池,最多同时运行5个线程。

ExecutorService executor = Executors.newFixedThreadPool(5);


单线程的Executor:

创建一个只有一个线程的线程池,所有任务按顺序执行。

ExecutorService executor = Executors.newSingleThreadExecutor();


缓存线程池:
创建一个可以根据需要创建新线程的线程池,适合执行短期异步任务。

ExecutorService executor = Executors.newCachedThreadPool();


计划任务的ScheduledExecutorService:
创建一个可以定时执行任务的线程池,适合执行周期性任务。

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5);
scheduler.scheduleAtFixedRate(new MyRunnable(), 0, 10, TimeUnit.SECONDS);

在本次案例中,使用了Executors.newFixedThreadPool(DOWNLOAD_THREAD_NUM + 1)来创建一个固定大小的线程池,用于管理下载任务和日志线程。这种方式确保了线程的复用,并且能够有效地控制线程的数量。

2.运行环境说明

Maven依赖如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wdbyte</groupId>
    <artifactId>down-bit</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.wdbyte.downbit.DownloadMain</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <!-- 这个jar-with-dependencies是assembly预先写好的一个,组装描述引用 -->
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <!--工程名-->
                    <finalName>${project.name}</finalName>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>
</project>

 项目整体结构如下

3.核心模块实现

3.1下载线程的实现

/**
 * 多线程下载工具类
 * @author 牵着猫散步的鼠鼠-LiuShiJie
 */
public class DownloadThread implements Callable<Boolean> {

    /**
     * 每次读取的数据块大小
     */
    private static int BYTE_SIZE = 1024 * 100;
    /**
     * 下载链接
     */
    private String url;
    /**
     * 下载开始位置
     */
    private long startPos;
    /**
     * 要下载的文件区块大小
     */
    private Long endPos;
    /**
     * 标识多线程下载切分的第几部分
     */
    private Integer part;
    /**
     * 文件总大小
     */
    private Long contentLenth;

    public DownloadThread(String url, long startPos, Long endPos, Integer part, Long contentLenth) {
        this.url = url;
        this.startPos = startPos;
        this.endPos = endPos;
        this.part = part;
        this.contentLenth = contentLenth;
    }

    @Override
    public Boolean call() throws Exception {
        if (url == null || url.trim() == "") {
            throw new RuntimeException("下载路径不正确");
        }

        // 文件名
        String httpFileName = HttpUtls.getHttpFileName(url);
        if (part != null) {
            httpFileName = httpFileName + DownloadMain.FILE_TEMP_SUFFIX + part;
        }

        // 本地文件大小
        Long localFileContentLength = FileUtils.getFileContentLength(httpFileName);
        LogThread.LOCAL_FINISH_SIZE.addAndGet(localFileContentLength);
        if (localFileContentLength >= endPos - startPos) {
            LogUtils.info("{} 已经下载完毕,无需重复下载", httpFileName);
            LogThread.DOWNLOAD_FINISH_THREAD.addAndGet(1);
            return true;
        }
        if (endPos.equals(contentLenth)) {
            endPos = null;
        }

        HttpURLConnection httpUrlConnection = HttpUtls.getHttpUrlConnection(url, startPos + localFileContentLength, endPos);
        // 获得输入流
        try (InputStream input = httpUrlConnection.getInputStream(); BufferedInputStream bis = new BufferedInputStream(input);
             RandomAccessFile oSavedFile = new RandomAccessFile(httpFileName, "rw")) {
            oSavedFile.seek(localFileContentLength);
            byte[] buffer = new byte[BYTE_SIZE];
            int len = -1;
            // 读到文件末尾则返回-1
            while ((len = bis.read(buffer)) != -1) {
                oSavedFile.write(buffer, 0, len);
                LogThread.DOWNLOAD_SIZE.addAndGet(len);
            }
        } catch (FileNotFoundException e) {
            LogUtils.error("ERROR! 要下载的文件路径不存在 {} ", url);
            return false;
        } catch (Exception e) {
            LogUtils.error("下载出现异常");
            e.printStackTrace();
            return false;
        } finally {
            httpUrlConnection.disconnect();
            LogThread.DOWNLOAD_FINISH_THREAD.addAndGet(1);
        }
        return true;
    }

}

3.2日志线程的实现

/**
 * 多线程下载日志记录
 * @author 牵着猫散步的鼠鼠-LiuShiJie
 */
public class LogThread implements Callable<Boolean> {

    // 本地下载的文件大小
    public static AtomicLong LOCAL_FINISH_SIZE = new AtomicLong();
    // 已经下载的文件大小
    public static AtomicLong DOWNLOAD_SIZE = new AtomicLong();
    // 下载完成的线程数
    public static AtomicLong DOWNLOAD_FINISH_THREAD = new AtomicLong();
    // 待下载的文件总大小
    private long httpFileContentLength;

    public LogThread(long httpFileContentLength) {
        this.httpFileContentLength = httpFileContentLength;
    }

    @Override
    public Boolean call() throws Exception {
        int[] downSizeArr = new int[5];
        int i = 0;
        double size = 0;
        double mb = 1024d * 1024d;
        // 文件总大小
        String httpFileSize = String.format("%.2f", httpFileContentLength / mb);
        while (DOWNLOAD_FINISH_THREAD.get() != DownloadMain.DOWNLOAD_THREAD_NUM) {
            double downloadSize = DOWNLOAD_SIZE.get();
            downSizeArr[++i % 5] = Double.valueOf(downloadSize - size).intValue();
            size = downloadSize;

            // 每秒速度
            double fiveSecDownloadSize = Arrays.stream(downSizeArr).sum();
            int speed = (int)((fiveSecDownloadSize / 1024d) / (i < 5d ? i : 5d));

            // 剩余时间
            double surplusSize = httpFileContentLength - downloadSize - LOCAL_FINISH_SIZE.get();
            String surplusTime = String.format("%.1f", surplusSize / 1024d / speed);
            if (surplusTime.equals("Infinity")) {
                surplusTime = "-";
            }

            // 已下大小
            String currentFileSize = String.format("%.2f", downloadSize / mb + LOCAL_FINISH_SIZE.get() / mb);
            String speedLog = String.format("> 已下载 %smb/%smb,速度 %skb/s,剩余时间 %ss", currentFileSize, httpFileSize, speed, surplusTime);
            System.out.print("\r");
            System.out.print(speedLog);

            // 一秒更新一次日志
            Thread.sleep(1000);
        }
        System.out.println();
        return true;
    }

}

3.3相关工具类的实现

文件操作工具类FileUtils ,主要用来获取文件大小长度

public class FileUtils {

    /**
     * 获取文件内容长度
     *
     * @param name
     * @return
     */
    public static long getFileContentLength(String name) {
        File file = new File(name);
        return file.exists() && file.isFile() ? file.length() : 0;
    }

}

 网络请求操作工具类HttpUtls,主要是一些常用的Http操作

/**
 * 网络请求操作工具类
 * @author 牵着猫散步的鼠鼠-LiuShiJie
 */
public class HttpUtls {

    /**
     * 获取 HTTP 链接
     *
     * @param url
     * @return
     * @throws IOException
     */
    public static HttpURLConnection getHttpUrlConnection(String url) throws IOException {
        URL httpUrl = new URL(url);
        HttpURLConnection httpConnection = (HttpURLConnection)httpUrl.openConnection();
        httpConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36");
        return httpConnection;
    }

    /**
     * 获取 HTTP 链接
     *
     * @param url
     * @param start
     * @param end
     * @return
     * @throws IOException
     */
    public static HttpURLConnection getHttpUrlConnection(String url, long start, Long end) throws IOException {
        HttpURLConnection httpUrlConnection = getHttpUrlConnection(url);
        LogUtils.debug("此线程下载内容区间 {}-{}", start, end);
        if (end != null) {
            httpUrlConnection.setRequestProperty("RANGE", "bytes=" + start + "-" + end);
        } else {
            httpUrlConnection.setRequestProperty("RANGE", "bytes=" + start + "-");
        }
        Map<String, List<String>> headerFields = httpUrlConnection.getHeaderFields();
        for (String s : headerFields.keySet()) {
            LogUtils.debug("此线程相应头{}:{}", s, headerFields.get(s));
        }
        return httpUrlConnection;
    }

    /**
     * 获取网络文件大小 bytes
     *
     * @param url
     * @return
     * @throws IOException
     */
    public static long getHttpFileContentLength(String url) throws IOException {
        HttpURLConnection httpUrlConnection = getHttpUrlConnection(url);
        int contentLength = httpUrlConnection.getContentLength();
        httpUrlConnection.disconnect();
        return contentLength;
    }

    /**
     * 获取网络文件 Etag
     *
     * @param url
     * @return
     * @throws IOException
     */
    public static String getHttpFileEtag(String url) throws IOException {
        HttpURLConnection httpUrlConnection = getHttpUrlConnection(url);
        Map<String, List<String>> headerFields = httpUrlConnection.getHeaderFields();
        List<String> eTagList = headerFields.get("ETag");
        httpUrlConnection.disconnect();
        return eTagList.get(0);
    }

    /**
     * 获取网络文件名
     *
     * @param url
     * @return
     */
    public static String getHttpFileName(String url) {
        int indexOf = url.lastIndexOf("/");
        return url.substring(indexOf + 1);
    }
}

日志工具类LogUtils ,主要负责日志格式化输出

/**
 * 日志工具类,输出日志
 * @author 牵着猫散步的鼠鼠-LiuShiJie
 */
public class LogUtils {

    public static boolean DEBUG = false;

    static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");

    public static void info(String msg, Object... arg) {
        print(msg, " -INFO- ", arg);
    }

    public static void error(String msg, Object... arg) {
        print(msg, " -ERROR-", arg);
    }

    public static void debug(String msg, Object... arg) {
        if (DEBUG) { print(msg, " -DEBUG-", arg); }
    }

    private static void print(String msg, String level, Object... arg) {
        if (arg != null && arg.length > 0) {
            msg = String.format(msg.replace("{}", "%s"), arg);
        }
        String thread = Thread.currentThread().getName();
        System.out.println(LocalDateTime.now().format(dateTimeFormatter) + " " + thread + level + msg);
    }
}

 迅雷链接转换工具类ThunderUtils ,迅雷链接与普通链接不同,需要转换

/**
 * 迅雷链接转换工具
 * @author 牵着猫散步的鼠鼠-LiuShiJie
 */
public class ThunderUtils {

    private static String THUNDER = "thunder://";

    /**
     * 判断是否是迅雷链接
     *
     * @param url
     * @return
     */
    public static boolean isThunderLink(String url) {
        return url.startsWith(THUNDER);
    }

    /**
     * 转换成 HTTP URL
     *
     * @param url
     * @return
     */
    public static String toHttpUrl(String url) {
        if (!isThunderLink(url)) {
            return url;
        }
        LogUtils.info("当前链接是迅雷链接,开始转换...");
        url = url.replaceFirst(THUNDER, "");
        try {
            // base 64 转换
            url = new String(Base64.getDecoder().decode(url.getBytes()), "UTF-8");
            // url 解码
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        // 去头去尾
        if (url.startsWith("AA")) {
            url = url.substring(2);
        }
        if (url.endsWith("ZZ")) {
            url = url.substring(0, url.length() - 2);
        }
        LogUtils.info("当前链接是迅雷链接,转换结果:{}", url);
        return url;
    }
}

3.4核心业务实现

/**
 * 多线程下载
 * 断点续传下载 demo
 * @author 牵着猫散步的鼠鼠-LiuShiJie
 */
public class DownloadMain {
    // 下载线程数量
    public static int DOWNLOAD_THREAD_NUM = 5;
    // 下载线程池
    private static ExecutorService executor = Executors.newFixedThreadPool(DOWNLOAD_THREAD_NUM + 1);
    // 临时文件后缀
    public static String FILE_TEMP_SUFFIX = ".temp";

    // 支持的 URL 协议
    private static HashSet<String> PROTOCAL_SET = new HashSet();

    static {
        PROTOCAL_SET.add("thunder://");
        PROTOCAL_SET.add("http://");
        PROTOCAL_SET.add("https://");
    }

    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要下载的链接:");
        String url = scanner.nextLine();
        long count = PROTOCAL_SET.stream().filter(prefix -> url.startsWith(prefix)).count();
        if (count == 0) {
            LogUtils.info("不支持的协议类型");
            return;
        }
        LogUtils.info("要下载的链接是:{}", url);
        new DownloadMain().download(ThunderUtils.toHttpUrl(url));
    }

    public void download(String url) throws Exception {
        String fileName = HttpUtls.getHttpFileName(url);
        long localFileSize = FileUtils.getFileContentLength(fileName);
        // 获取网络文件具体大小
        long httpFileContentLength = HttpUtls.getHttpFileContentLength(url);
        if (localFileSize >= httpFileContentLength) {
            LogUtils.info("{}已经下载完毕,无需重新下载", fileName);
            return;
        }
        List<Future<Boolean>> futureList = new ArrayList<>();
        if (localFileSize > 0) {
            LogUtils.info("开始断点续传 {}", fileName);
        } else {
            LogUtils.info("开始下载文件 {}", fileName);
        }
        LogUtils.info("开始下载时间 {}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        long startTime = System.currentTimeMillis();
        // 任务切分
        splitDownload(url, futureList);
        LogThread logThread = new LogThread(httpFileContentLength);
        Future<Boolean> future = executor.submit(logThread);
        futureList.add(future);
        // 开始下载
        for (Future<Boolean> booleanFuture : futureList) {
            booleanFuture.get();
        }
        LogUtils.info("文件下载完毕 {},本次下载耗时:{}", fileName, (System.currentTimeMillis() - startTime) / 1000 + "s");
        LogUtils.info("结束下载时间 {}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        // 文件合并
        boolean merge = merge(fileName);
        if (merge) {
            // 清理分段文件
            clearTemp(fileName);
        }
        LogUtils.info("本次文件下载结束,下载位置为" + fileName);
        System.exit(0);
    }

    /**
     * 切分下载任务到多个线程
     *
     * @param url
     * @param futureList
     * @throws IOException
     */
    public void splitDownload(String url, List<Future<Boolean>> futureList) throws IOException {
        long httpFileContentLength = HttpUtls.getHttpFileContentLength(url);
        // 任务切分
        long size = httpFileContentLength / DOWNLOAD_THREAD_NUM;
        long lastSize = httpFileContentLength - (httpFileContentLength / DOWNLOAD_THREAD_NUM * (DOWNLOAD_THREAD_NUM - 1));
        for (int i = 0; i < DOWNLOAD_THREAD_NUM; i++) {
            long start = i * size;
            Long downloadWindow = (i == DOWNLOAD_THREAD_NUM - 1) ? lastSize : size;
            Long end = start + downloadWindow;
            if (start != 0) {
                start++;
            }
            DownloadThread downloadThread = new DownloadThread(url, start, end, i, httpFileContentLength);
            Future<Boolean> future = executor.submit(downloadThread);
            futureList.add(future);
        }
    }

    public boolean merge(String fileName) throws IOException {
        LogUtils.info("开始合并文件 {}", fileName);
        byte[] buffer = new byte[1024 * 10];
        int len = -1;
        try (RandomAccessFile oSavedFile = new RandomAccessFile(fileName, "rw")) {
            for (int i = 0; i < DOWNLOAD_THREAD_NUM; i++) {
                try (BufferedInputStream bis = new BufferedInputStream(
                    new FileInputStream(fileName + FILE_TEMP_SUFFIX + i))) {
                    while ((len = bis.read(buffer)) != -1) { // 读到文件末尾则返回-1
                        oSavedFile.write(buffer, 0, len);
                    }
                }
            }
            LogUtils.info("文件合并完毕 {}", fileName);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    public boolean clearTemp(String fileName) {
        LogUtils.info("开始清理临时文件 {}{}0-{}", fileName, FILE_TEMP_SUFFIX, (DOWNLOAD_THREAD_NUM - 1));
        for (int i = 0; i < DOWNLOAD_THREAD_NUM; i++) {
            File file = new File(fileName + FILE_TEMP_SUFFIX + i);
            file.delete();
        }
        LogUtils.info("临时文件清理完毕 {}{}0-{}", fileName, FILE_TEMP_SUFFIX, (DOWNLOAD_THREAD_NUM - 1));
        return true;
    }

    /**
     * 使用CheckedInputStream计算CRC
     */
    public static Long getCRC32(String filepath) throws IOException {
        InputStream inputStream = new BufferedInputStream(new FileInputStream(filepath));
        CRC32 crc = new CRC32();
        byte[] bytes = new byte[1024];
        int cnt;
        while ((cnt = inputStream.read(bytes)) != -1) {
            crc.update(bytes, 0, cnt);
        }
        inputStream.close();
        return crc.getValue();
    }

}

4.功能测试

启动main程序,输入下载链接,我们这里使用腾讯QQ安装包的CDN链接(https://dldir1.qq.com/qqfile/qq/PCQQ9.7.17/QQ9.7.17.29225.exe)来测试下载功能

文件下载成功,在项目上级目录文件完整,能够正常使用 

 

总结

本文通过详细解析一个多线程下载器的实现,深入探讨了Java多线程编程的基础知识、工具类的使用以及核心模块的实现。我们学习了如何创建和启动线程、线程的同步和互斥、以及线程池的使用,这些都是多线程下载器的基础。同时,我们了解了如何使用Java的标准库来实现文件操作、网络请求、日志记录等功能。

希望文章对您的学习有帮助,有时间会继续出Java并发编程相关的内容~

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

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

相关文章

JVM学习之常见知识点汇总、2024详细版面试问题汇总;JVM组成、类加载器、GC垃圾回收、堆、栈、方法区

目录 JVM组成 什么是程序计数器 详细的介绍一下Java的堆 什么是虚拟机栈 堆和栈的区别 堆空间的分配策略 对于方法区的解释 IO和NIO拷贝数据的对比 JVM内存结构 JVM去除永久代改用元空间替代的原因 类加载器 什么是类加载器&#xff0c;类加载器有哪些 什么是双亲…

吴恩达机器学习-可选实验室:可选实验:使用逻辑回归进行分类(Classification using Logistic Regression)

在本实验中&#xff0c;您将对比回归和分类。 import numpy as np %matplotlib widget import matplotlib.pyplot as plt from lab_utils_common import dlc, plot_data from plt_one_addpt_onclick import plt_one_addpt_onclick plt.style.use(./deeplearning.mplstyle)jupy…

计算机组成原理实验报告1 | 实验1.1 运算器实验(键盘方式)

本文整理自博主大学本科《计算机组成原理》课程自己完成的实验报告。 —— *实验环境为学校机房实验箱。 目录 一、实验目的 二、实验内容 三、实验步骤及实验结果 Ⅰ、单片机键盘操作方式实验 1、实验连线&#xff08;键盘实验&#xff09; 2、实验过程 四、实验结果的…

C语言:深入补码计算原理

C语言&#xff1a;深入补码计算原理 有符号整数存储原码、反码、补码转换规则数据与内存的关系 补码原理 有符号整数存储 原码、反码、补码 有符号整数的2进制表示方法有三种&#xff0c;即原码、反码和补码 三种表示方法均有符号位和数值位两部分&#xff0c;符号位用0表示“…

25改考408最新资讯!拷贝

东北大学 东北大学计算机考研全面改考408 东北大学计算机学院官网&#xff1a;http://www.cse.neu.edu.cn/6274/list.htm 东北大学计算机考研全面改考408 公告原文 东北大学计算机科学与工程学院关于调整2025年硕士研究生招生计算机科学与技术、计算机技术、人工智能专业初试…

NBlog整合OSS图库

NBlog部署维护流程记录&#xff08;持续更新&#xff09;&#xff1a;https://blog.csdn.net/qq_43349112/article/details/136129806 由于项目是fork的&#xff0c;所以我本身并不清楚哪里使用了图床&#xff0c;因此下面就是我熟悉项目期间边做边调整的。 目前已经调整的功能…

机器学习评价指标(分类、目标检测)

https://zhuanlan.zhihu.com/p/364253497https://zhuanlan.zhihu.com/p/46714763https://blog.csdn.net/u013250861/article/details/123029585 1.1 混淆矩阵 在介绍评价指标之前&#xff0c;我们首先要介绍一下混淆矩阵&#xff08;confusion matrix&#xff09;。混淆矩阵…

数据结构小记【Python/C++版】——B树篇

一&#xff0c;基础概念 B树也是一种自平衡搜索树&#xff0c;常用于数据库中索引的实现。 B树和AVL树的区别在于&#xff1a; B树是一种多路平衡查找树&#xff0c;B树的节点可以有两个以上的子节点(AVL树是二叉树&#xff0c;最多只能有两个子节点)。 B树的每个节点可以存…

什么是SSL洪水攻击,有办法处理吗?

相信不少人曾遇到过或是听说过DDOS攻击&#xff0c;分布式拒绝服务&#xff08;DDoS&#xff09;攻击指多个系统联合进行攻击&#xff0c;从而使目标机器停止提供服务或资源访问。大致来说&#xff0c;就是让多台电脑向一台服务器发送大量的流量&#xff0c;直到该服务器崩溃掉…

利用GPT开发应用007:警惕人工智能幻觉,局限与注意事项

文章目录 一、人工智能幻觉二、计算案例三、斑马案例四、总结 正如您所见&#xff0c;一个大型语言模型通过基于给定的输入提示逐个预测下一个单词&#xff08;或标记&#xff09;来生成答案。在大多数情况下&#xff0c;模型的输出对您的任务来说是相关的&#xff0c;并且完全…

解决Ubuntu 16.04/18.04 图形化界面异常、鼠标光标消失、鼠标变成叉叉等问题

bug场景&#xff1a; 一切从一次换源说起…叭叭叭 这篇文章解决的问题&#xff1a; 1.换源&#xff0c;默认源太慢&#xff0c;换成可用的阿里云的源 2.apt-get failed to …问题 3.图形化异常问题 4.get unmet dependence 问题 5. 鼠标光标消失和鼠标变成叉叉问题。 解决方…

【计算机网络_应用层】https协议——加密和窃密的攻防

文章目录 1.https协议的介绍2. 加密和解密2.1 什么是加密2.2 常见的加密方式2.2.1 对称加密2.2.2 非对称加密 2.3 数据摘要&#xff08;数据指纹&#xff09;2.4 数字签名 3. https协议的加密和解密方案一&#xff1a;使用对称加密&#xff08;❌&#xff09;方案二&#xff1a…

搜维尔科技:动作捕捉与数字时尚:Wondar Studios欧莱雅项目

来自意大利的Wondar Studios工作室&#xff0c;是一家制作与动作捕捉技术相关软件和内容的公司&#xff0c;其出品的三维角色动画均由专业动捕系统真实录制制作。 我们很高兴与大家分享Wondar Studios最新的动捕项目&#xff0c;该项目带来了身临其境的虚拟现实体验。他们与巴…

加密流量分类torch实践4:TrafficClassificationPandemonium项目更新

加密流量分类torch实践4&#xff1a;TrafficClassificationPandemonium项目更新 更新日志 代码已经推送开源至露露云的github&#xff0c;如果能帮助你&#xff0c;就给鼠鼠点一个star吧&#xff01;&#xff01;&#xff01; 3/10号更新 流量预处理更新 增加了基于splitCa…

JavaScript基础6之执行上下文、作用域链、函数创建、函数激活、checkScope的执行过程、闭包、this

JavaScript基础 执行上下文执行上下文中的属性变量对象全局上下文的变量对象函数上下文执行过程进入执行上下文代码执行思考题 作用域链函数创建函数激活checkScope的执行过程总结 闭包分析闭包 this 执行上下文 执行上下文中的属性 每一个执行上下文都有三个核心属性 变量对…

03-安装配置jenkins

一、安装部署jenkins 1&#xff0c;上传软件包 为了方便学习&#xff0c;本次给大家准备了百度云盘的安装包 链接&#xff1a;https://pan.baidu.com/s/1_MKFVBdbdFaCsOTpU27f7g?pwdq3lx 提取码&#xff1a;q3lx [rootjenkins ~]# rz -E [rootjenkins ~]# yum -y localinst…

Linux学习:权限

目录 1. shell命令的工作原理与存在意义1.1 shell命令解释器存在的意义1.2 shell解释器的工作原理 2. Linux操作系统&#xff1a;用户2.1 什么是用户2.2 用户的切换操作2.3 用户权限划分的意义 3. Linux中权限的种类和意义3.1 什么是权限3.2 sudo指令与短暂提权 4. 文件类型与文…

03-快速上手RabbitMQ的5种消息模型

RabbitMQ RabbitMQ是基于Erlang语言开发的开源消息通信中间件,有几个常见概念 connections(连接): 将来publisher(消息的发送者)或者consumer(消息的接收者)都需要先与MQ建立连接 channel(通道): 建立连接后需要创建通道,生产者和消费者就是基于通道完成消息的发送和接收 ex…

网络安全:OpenEuler 部署 jumpserver 堡垒机

目录 一、实验 1.环境 2.OpenEuler 部署 jumpserver 堡垒机 3.OpenEuler 使用 jumpserver 堡垒机&#xff08;管理Linux&#xff09; 4.OpenEuler 使用 jumpserver 堡垒机&#xff08;管理Windows&#xff09; 二、问题 1.jumpserver 安装报错 一、实验 1.环境 &#x…

linux paddle For C++环境搭建

paddle介绍 Paddle是类似tesseract的文字识别ocr。因为tesseract-ocr的中文识别效果不好。因此才准备安装Paddle。Paddle最方便的安装方式的使用Python的包管理安装。pip3 install paddlepaddle。但我使用了一下感觉还是用C更加方便&#xff0c;QT OpenCV Paddle应当还不错。…