java多线程文件下载器

文章目录

  • 1.简介
  • 2.文件下载的核心
  • 3.文件下载器的基础代码
    • 3.1 HttpURLConnection
    • 3.2 用户标识
  • 4.下载信息
    • 4.1 计划任务
    • 4.2 ScheduledExecutorService
      • 🍀 schedule方法
      • 🍀 scheduleAtFixedRate方法
      • 🍀 scheduleWithFixedDelay方法
  • 5.线程池简介
    • 5.1 ThreadPoolExecutor 构造方法参数
    • 5.2 线程池工作过程
    • 5.3 线程池的状态
    • 5.4 线程池的关闭
    • 5.5 工作队列
  • 6.代码实现
    • 6.1 环境搭建
      • 🍀 基本信息
      • 🍀 创建项目
    • 6.2 实现逻辑
    • 6.3 项目结构
    • 6.4 类代码
      • 🍀 constant 包
        • 📌 Constant
      • 🍀 util 包
        • 📌 FileUtils
        • 📌 HttpUtils
        • 📌 LogUtils
      • 🍀 core 包
        • 📌 DownloadInfoThread
        • 📌 DownloaderTask
        • 📌 Downloader
      • 🍀 Main 主类

1.简介

该项目应用的知识点包括:

  • RandomAccessFile 类的运用
  • HttpURLConnection 类的运用
  • 线程池的使用
  • 原子类 LongAdder 的运用
  • CountDownLatch 类的运用
  • ScheduledExecutorService 类的运用

2.文件下载的核心

从互联网下载文件有点类似于我们将本地某个文件复制到另一个目录下,也会利用 IO 流进行操作。对于从互联网下载,还需要将本地和下载文件所在的服务器建立连接。

image-20231107124520655

3.文件下载器的基础代码

3.1 HttpURLConnection

从互联网中下载文件的话,需要与文件所在的服务器建立连接,这里可以使用 jdk 提供的 java.net.HttpURLConnection 类来帮助我们完成这个操作。jdk11中有提供 java.net.http.HttpClient 类来替代 HttpURLConnection,由于现在使用的是 jdk8,因此先不用 jdk11 中的 HttpClient。除此之外还有一些其他第三方提供类可以执行类似的操作,这里就不赘述了。

3.2 用户标识

我们通过浏览器访问某个网站的时候,会将当前浏览器的版本,操作系统版本等信息的标识发送到网站所在的服务器中。当用程序代码去访问网站时,需要将这个标识发送过去。

Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1

4.下载信息

4.1 计划任务

文件下载的时候最好能够展示出下载的速度,已下载文件大小等信息。这里可以每隔一段时间来获取文件的下载信息,比如间隔 1 秒获取一次,然后将信息打印到控制台。文件下载是一个独立的线程,另外还需要再开启一个线程来间隔获取文件的信息。java.util.concurrent.ScheduledExecutorService 这个类可以帮助我们来实现此功能。

4.2 ScheduledExecutorService

在该类中提供了一些方法可以帮助开发者实现间隔执行的效果,下面列出一些常见的方法及其参数说明。我们可以通过下面方式来获取该类的对象,其中 1 标识核心线程的数量。

ScheduledExecutorService s = Executors.newScheduledThreadPool(1);

🍀 schedule方法

该方法是重载的,这两个重载的方法都是有 3 个形参,只是第一个形参不同。

参数含义
Runnable / Callable<V>可以传入这两个类型的任务
long delay延时的时间数量
TimeUnit unit时间单位

该方法的作用是让任务按照指定的时间延时执行。

🍀 scheduleAtFixedRate方法

该方法的作用是按照指定的时间延时执行,并且每隔一段时间再继续执行。

参数含义
Runnable command执行的任务
long initialDelay延时的时间数量
long period间隔的时间数量
TimeUnit unit时间单位

倘若在执行任务的时候,耗时超过了间隔时间,则任务执行结束之后直接再次执行,而不是再等待间隔时间执行。

🍀 scheduleWithFixedDelay方法

该方法的作用是按照指定的时间延时执行,并且每隔一段时间再继续执行。

参数含义
Runnable command执行的任务
long initialDelay延时的时间数量
long period间隔的时间数量
TimeUnit unit时间单位

在执行任务的时候,无论耗时多久,任务执行结束之后都会等待间隔时间之后再继续下次任务。

5.线程池简介

线程在创建,销毁的过程中会消耗一些资源,为了节省这些开销,jdk 添加了线程池。线程池节省了开销,提高了线程使用的效率。阿里巴巴开发文档中建议在编写多线程程序的时候使用线程池。

5.1 ThreadPoolExecutor 构造方法参数

在 juc 包下提供了 ThreadPoolExecutor 类,可以通过该类来创建线程池,这个类中有4个重载的构造方法,最核心的构造方法是有7个形参的,这些参数所代表的意义如下:

参数含义
corePoolSize线程池中核心线程的数量
maximumPoolSize线程池中最大线程的数量,是核心线程数量和非核心线程数量之和
keepAliveTime非核心线程空闲的生存时间
unitkeepAliveTime 的生存时间单位
workQueue当没有空闲的线程时,新的任务会加入到 workQueue 中排队等待
threadFactory线程工厂,用于创建线程
handler拒绝策略,当任务太多无法处理时的拒绝策略

5.2 线程池工作过程

image-20231108082712983

5.3 线程池的状态

状态说明
RUNNING创建线程池之后的状态是 RUNNING
SHUTDOWN该状态下,线程池就不会接收新任务,但会处理阻塞队列剩余任务,相对温和
STOP该状态下会中断正在执行的任务,并抛弃阻塞队列任务,相对暴力
TIDYING任务全部执行完毕,活动线程为 0 即将进入终止
TERMINATED线程池终止

5.4 线程池的关闭

线程池使用完毕之后需要进行关闭,提供了以下两种方法进行关闭。

方法说明
shutdown()该方法执行后,线程池状态变为 SHUTDOWN,不会接收新任务,但是会执行完已提交的任务,此方法不会阻塞调用线程的执行。
shutdownNow()该方法执行后,线程池状态变为 STOP,不会接收新任务,会将队列中的任务返回,并用 interrupt 的方式中断正在执行的任务。

5.5 工作队列

jdk 中提供的一些工作队列 workQueue。

队列说明
SynchronousQueue直接提交队列
ArrayBlockingQueue有界队列,可以指定容量
LinkedBlockingDeque无界队列
PriorityBlockingQueue优先任务队列,可以根据任务优先级顺序执行任务

6.代码实现

6.1 环境搭建

🍀 基本信息

  • 开发工具:IDEA
  • JDK 版本:8
  • 项目编码:utf-8

🍀 创建项目

在开发工具中创建一个 javase 项目即可,无需导入第三方 jar 依赖。

6.2 实现逻辑

  1. 先判断是否已存在重复文件,该步骤其实可忽略,因为最终下载合并的文件名已采用时间戳进行了唯一标识;
  2. 启动一个线程每隔一秒打印下载情况;
  3. 切分任务,多线程分快下载;
  4. 全部块文件下载完毕,合并分块文件;
  5. 合并分块文件完毕,清理分块文件;
  6. 释放资源,关闭线程池和连接对象。

6.3 项目结构

image-20231108091928709

包名作用
constant存放常量类的包
core存放了下载器核心类的包
util存放工具类的包
Main主类

6.4 类代码

🍀 constant 包

📌 Constant
/**
 * Description: 存放项目常量
 *
 * @Author 狐狸半面添
 * @Create 2023/11/6 1:22
 * @Version 1.0
 */
public class Constant {
    /**
     * 指定下载目录的存放位置
     */
    public static final String PATH = "D:\\download\\";

    public static final double MB = 1024d * 1024d;
    public static final double KB = 1024d;

    /**
     * 每次读取的字节大小
     */
    public static final int BYTE_SIZE = 1024 * 100;

    /**
     * 块文件(临时文件)的后缀
     */
    public static final String PART_FILE_SUFFIX = ".temp";

    /**
     * 线程数量
     */
    public static final int THREAD_NUM = 5;

    // 创建存放位置的代码
    // public static void main(String[] args) {
    //     File file = new File("D:\\download");
    //     if (!file.exists()) {
    //         file.mkdir();
    //     }
    // }
}

🍀 util 包

📌 FileUtils
/**
 * Description: 文件相关工具
 *
 * @Author 狐狸半面添
 * @Create 2023/11/6 11:46
 * @Version 1.0
 */
public class FileUtils {
    /**
     * 获取本地文件的大小
     *
     * @param path 文件路径
     * @return 文件大小
     */
    public static long getFileContentLength(String path) {
        File file = new File(path);
        return file.exists() && file.isFile() ? file.length() : 0;
    }
}
📌 HttpUtils
/**
 * Description: Http 相关工具类
 *
 * @Author 狐狸半面添
 * @Create 2023/11/6 1:06
 * @Version 1.0
 */
public class HttpUtils {

    private static long id = System.currentTimeMillis();

    public static void change() {
        id = System.currentTimeMillis();
    }

    /**
     * 获取下载的文件大小
     *
     * @param url 下载文件链接
     * @return 文件大小
     * @throws IOException
     */
    public static long getHttpFileContentLength(String url) throws IOException {
        int contentLength;
        HttpURLConnection httpURLConnection = null;
        try {
            httpURLConnection = getHttpURLConnection(url);
            contentLength = httpURLConnection.getContentLength();
        } finally {
            if (httpURLConnection != null) {
                httpURLConnection.disconnect();
            }
        }
        return contentLength;
    }

    /**
     * 分块下载
     *
     * @param url      下载地址
     * @param startPos 下载文件起始位置
     * @param endPos   下载文件结束位置
     * @return 连接对象
     */
    public static HttpURLConnection getHttpURLConnection(String url, long startPos, long endPos) throws IOException {
        HttpURLConnection httpURLConnection = getHttpURLConnection(url);
        LogUtils.info("下载的区间是:{}-{}", startPos, endPos);

        if (endPos != 0) {
            httpURLConnection.setRequestProperty("RANGE", "bytes=" + startPos + "-" + endPos);
        } else {
            httpURLConnection.setRequestProperty("RANGE", "bytes=" + startPos + "-");
        }

        return httpURLConnection;
    }

    /**
     * 获取 HttpURLConnection 连接对象
     *
     * @param url 文件的地址
     * @return HttpURLConnection 连接对象
     */
    public static HttpURLConnection getHttpURLConnection(String url) throws IOException {
        URL httpUrl = new URL(url);
        HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
        // 向文件所在的服务器发送标识信息
        httpURLConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1");
        return httpURLConnection;
    }

    /**
     * 获取下载文件的名字
     *
     * @param url 下载地址
     * @return 文件名
     */
    public static String getHttpFileName(String url) {

        String fileName;

        int startIndex = url.lastIndexOf("/");
        int endIndex = url.lastIndexOf("?");
        if (endIndex == -1) {
            fileName = url.substring(startIndex + 1);
        } else {
            fileName = url.substring(startIndex + 1, endIndex);
        }

        int pointIndex = fileName.lastIndexOf(".");

        return fileName.substring(0, fileName.lastIndexOf(".")) + "-" + id + fileName.substring(pointIndex);
    }

}
📌 LogUtils
/**
 * Description: 日志工具类
 *
 * @Author 狐狸半面添
 * @Create 2023/11/6 1:41
 * @Version 1.0
 */
public class LogUtils {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("hh:mm:ss");

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

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

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

🍀 core 包

📌 DownloadInfoThread
/**
 * Description: 展示下载信息
 *
 * @Author 狐狸半面添
 * @Create 2023/11/6 2:07
 * @Version 1.0
 */
@SuppressWarnings("AlibabaUndefineMagicConstant")
public class DownloadInfoThread implements Runnable {
    /**
     * 下载文件总大小
     */
    private final long httpFileContentLength;


    /**
     * 本次累计下载的大小
     */
    public static volatile LongAdder downSize = new LongAdder();

    /**
     * 前一次下载的大小
     */
    public double prevSize;

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

    @Override
    public void run() {
        // 计算文件总大小 单位是 MB
        String httpFileSize = String.format("%.2f", httpFileContentLength / Constant.MB);

        // 计算每秒下载速度 kb
        int speed = (int) ((downSize.doubleValue() - prevSize) / Constant.KB);

        prevSize = downSize.doubleValue();

        // 剩余文件的大小
        double remainSize = httpFileContentLength - downSize.doubleValue();

        // 计算剩余时间
        String remainTime = String.format("%.1f", remainSize / Constant.KB / speed);

        if ("Infinity".equalsIgnoreCase(remainTime)) {
            remainTime = "-";
        }

        // 已下载大小
        String currentFileSize = String.format("%.1f", downSize.doubleValue() / Constant.MB);

        String speedInfo = String.format("已下载 %smb/%smb,速度 %skb/s,剩余时间 %ss", currentFileSize, httpFileSize, speed, remainTime);

        System.out.print("\r");
        System.out.print(speedInfo);

    }
}
📌 DownloaderTask
/**
 * Description: 分块下载任务
 *
 * @Author 狐狸半面添
 * @Create 2023/11/7 0:58
 * @Version 1.0
 */
public class DownloaderTask implements Callable<Boolean> {

    private final String url;

    /**
     * 下载起始位置
     */
    private final long startPos;

    /**
     * 下载结束位置
     */
    private final long endPos;

    /**
     * 标识当前是哪一部分
     */
    private final int part;

    private final CountDownLatch countDownLatch;

    public DownloaderTask(String url, long startPos, long endPos, int part, CountDownLatch countDownLatch) {
        this.url = url;
        this.startPos = startPos;
        this.endPos = endPos;
        this.part = part;
        this.countDownLatch = countDownLatch;
    }

    @Override
    public Boolean call() throws Exception {
        // 获取文件名
        String httpFileName = HttpUtils.getHttpFileName(url);
        // 分块的文件名
        httpFileName = httpFileName + Constant.PART_FILE_SUFFIX + part;
        // 下载路径
        httpFileName = Constant.PATH + httpFileName;

        // 获取分块下载的连接
        HttpURLConnection httpURLConnection = HttpUtils.getHttpURLConnection(url, startPos, endPos);

        try (
                InputStream input = httpURLConnection.getInputStream();
                BufferedInputStream bis = new BufferedInputStream(input);
                RandomAccessFile accessFile = new RandomAccessFile(httpFileName, "rw");
        ) {
            byte[] buffer = new byte[Constant.BYTE_SIZE];
            int len;
            // 循环读取数据
            while ((len = bis.read(buffer)) != -1) {
                // 1s 内下载的数据,通过原子类下载
                DownloadInfoThread.downSize.add(len);
                accessFile.write(buffer, 0, len);
            }
        } catch (FileNotFoundException e) {
            LogUtils.error("下载文件不存在 {}", url);
            return false;
        } catch (Exception e) {
            LogUtils.error("下载出现异常");
            return false;
        } finally {
            httpURLConnection.disconnect();
            countDownLatch.countDown();
        }

        return true;
    }

}
📌 Downloader
/**
 * Description: 下载器
 *
 * @Author 狐狸半面添
 * @Create 2023/11/6 1:21
 * @Version 1.0
 */
public class Downloader {

    private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

    public ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(Constant.THREAD_NUM,
            Constant.THREAD_NUM,
            0,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(5));

    private CountDownLatch countDownLatch = new CountDownLatch(Constant.THREAD_NUM);

    public void download(String url) {
        // 获取文件名
        String httpFileName = HttpUtils.getHttpFileName(url);
        // 文件下载路径
        httpFileName = Constant.PATH + httpFileName;
        // 获取本地文件的大小
        long localFileLength = FileUtils.getFileContentLength(httpFileName);


        HttpURLConnection httpURLConnection = null;
        DownloadInfoThread downloadInfoThread;
        try {
            // 获取连接对象
            httpURLConnection = HttpUtils.getHttpURLConnection(url);

            // 获取下载文件的总大小
            int contentLength = httpURLConnection.getContentLength();

            // 判断文件是否已下载过
            if (localFileLength >= contentLength) {
                LogUtils.info("{} 已下载完毕,无需重新下载", httpFileName);
                // 关闭连接对象
                httpURLConnection.disconnect();
                // 关闭线程池
                scheduledExecutorService.shutdownNow();
                poolExecutor.shutdown();

                return;
            }

            // 创建获取下载信息的任务对象
            downloadInfoThread = new DownloadInfoThread(contentLength);

            // 将任务交给线程执行,每隔 1s 打印一次
            scheduledExecutorService.scheduleAtFixedRate(downloadInfoThread, 1, 1, TimeUnit.SECONDS);

            // 切分任务
            ArrayList<Future> list = new ArrayList<>();
            split(url, list);

            countDownLatch.await();

            System.out.print("\r");
            System.out.println("分块文件下载完成");

            // 合并文件
            if (merge(httpFileName)) {
                // 清除临时文件
                clearTemp(httpFileName);
            }


        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("本次执行完成");

            // 关闭连接对象
            if (httpURLConnection != null) {
                httpURLConnection.disconnect();
            }

            // 关闭线程池
            scheduledExecutorService.shutdownNow();
            poolExecutor.shutdown();
        }
    }

    /**
     * 文件切分
     *
     * @param url        文件链接
     * @param futureList 任务集合
     */
    public void split(String url, ArrayList<Future> futureList) {
        try {
            // 获取下载文件大小
            long contentLength = HttpUtils.getHttpFileContentLength(url);

            // 计算切分后的文件大小
            long size = contentLength / Constant.THREAD_NUM;

            // 计算分块个数
            for (int i = 0; i < Constant.THREAD_NUM; i++) {
                // 计算下载起始位置
                long startPos = i * size;

                // 计算结束位置
                long endPos;
                if (i == Constant.THREAD_NUM - 1) {
                    // 下载最后一块
                    endPos = 0;
                } else {
                    endPos = startPos + size - 1;
                }

                // 创建任务对象
                DownloaderTask downloaderTask = new DownloaderTask(url, startPos, endPos, i, countDownLatch);
                // 将任务提交到线程池
                Future<Boolean> future = poolExecutor.submit(downloaderTask);

                futureList.add(future);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 文件合并
     *
     * @param fileName 文件名
     * @return 是否合并成功
     */
    public boolean merge(String fileName) {
        LogUtils.info("开始合并文件 {}", fileName);
        byte[] buffer = new byte[Constant.BYTE_SIZE];
        int len;
        try (
                RandomAccessFile accessFile = new RandomAccessFile(fileName, "rw")
        ) {
            for (int i = 0; i < Constant.THREAD_NUM; i++) {
                try (
                        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName + Constant.PART_FILE_SUFFIX + i))
                ) {
                    while ((len = bis.read(buffer)) != -1) {
                        accessFile.write(buffer, 0, len);
                    }

                }
            }

            LogUtils.info("文件合并完毕 {}", fileName);

        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    /**
     * 清除临时文件
     *
     * @param fileName 文件名
     */
    public void clearTemp(String fileName) {
        LogUtils.info("清理分块文件");
        for (int i = 0; i < Constant.THREAD_NUM; i++) {
            String name = fileName + Constant.PART_FILE_SUFFIX + i;
            File file = new File(name);
            file.delete();
        }
        LogUtils.info("分块清除完毕");
    }
}

🍀 Main 主类

public class Main {
    public static void main(String[] args) {
        // 下载地址
        String url = null;

        if (args == null || args.length == 0) {
            while (url == null || url.trim().isEmpty()) {
                System.out.print("请输入下载链接:");
                Scanner scanner = new Scanner(System.in);
                url = scanner.next();
            }
        } else {
            url = args[0];
        }

        Downloader downloader = new Downloader();
        downloader.download(url);

    }
}

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

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

相关文章

“隐身术”成现实,中科院院士现场表演

&#xff08;图源&#xff1a;哔哩哔哩&#xff09; 在“bilibili超级科学晚”活动现场&#xff0c;中国科学院院士褚君浩为我们揭示了“隐身术”的原理。原来&#xff0c;这种神奇的技能是一种科学手段。 褚君浩院士为大家介绍了一种名为“柱镜光栅”的特殊材料&#xff0c;柱…

Zotero拓展功能之Zotero Style

Zotero Style拓展功能 一、列&#xff1a; 1.简介 首先你必须知道Zotero的基本功能&#xff1a;右键任意一个列的名字&#xff0c;会弹出一个右键菜单&#xff0c;你可以勾选/取消勾选一个列&#xff0c;并且在最后有两个按钮&#xff0c;一个是“列设置”&#xff0c;一个是…

VS2015模块库交接出现环境报错 error MSB8031 和 error C1189

问题报错 1.错误 MSB8031 Building an MFC project for a non-Unicode character set is deprecated. You must change the project property to Unicode or download an additional library. 错误 MSB8031不赞成为非Unicode字符集生成MFC项目。您必须将项目属性更改为Unicode&…

数据公网传输加密隧道技术

参考&#xff1a; https://wenku.baidu.com/view/c2bfb9b4d6bbfd0a79563c1ec5da50e2524dd1a1.html?wkts1699578126402

【Linux基础IO篇】用户缓冲区、文件系统、以及软硬链接

【Linux基础IO篇】用户缓冲区、文件系统、以及软硬链接 目录 【Linux基础IO篇】用户缓冲区、文件系统、以及软硬链接深入理解用户缓冲区缓冲区刷新问题缓冲区存在的意义 File模拟实现C语言中文件标准库 文件系统认识磁盘对目录的理解 软硬链接软硬链接的删除文件的三个时间 作者…

【Excel】函数sumif范围中符合指定条件的值求和

SUMIF函数是Excel常用函数。使用 SUMIF 函数可以对报表范围中符合指定条件的值求和。 Excel中sumif函数的用法是根据指定条件对若干单元格、区域或引用求和。 sumif函数语法是&#xff1a;SUMIF(range&#xff0c;criteria&#xff0c;sum_range) sumif函数的参数如下&#xff…

安徽首届道医传承十八绝技发布会在合肥成功举办

近日&#xff0c;在安徽合肥举行了首届道医传承十八绝技发布会&#xff0c;本次会议由安徽渡罗门生物科技有限公司、北京道武易医文化传播有限公司、楼观台道医文化研究院联合举办。现场吸引了来自全国各地民族医学领域的专家学者参与讨论与交流。本次会议旨在促进道医的交流与…

OCR技术狂潮:揭秘最新发展现状,引爆未来智能时代

OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;技术自20世纪以来经历了长足的发展&#xff0c;随着计算机视觉、人工智能和深度学习等领域的进步&#xff0c;OCR技术在准确性、速度和适用范围上都取得了显著的进展。以下是OCR技术发展的现…

在gitlab中指定自定义 CI/CD 配置文件

文章目录 1. 介绍2. 配置操作3. 配置场景3.1 CI/CD 配置文件在当前项目step1&#xff1a;在当前项目中创建目录&#xff0c;编写流水线文件存放在该目录中step2&#xff1a;在当前项目中配置step3&#xff1a;运行流水线测试 3.2 CI/CD 配置文件位于外部站点上step1&#xff1a…

嵌入式养成计划-47----QT--基于QT的OpenCV库实现人脸识别功能

一百二十一、基于QT的OpenCV库实现人脸识别功能 121.1 UI 界面 登录按钮现在没啥实际作用&#xff0c;因为没加功能&#xff0c;可以添加在识别成功后运行的功能代码 121.2 思路 显示人脸&#xff1a; 通过 VideoCapture 这个类下面的 open() 方法打开摄像头&#xff0c;对…

进口猫罐头在排行榜中是否靠前?排行榜中靠前的猫罐头测评

养猫这6年&#xff0c;我对猫咪的日常饮食把关一直很严格。这些年我给我家猫们购买过很多不同品牌、不同口味的罐头&#xff0c;在猫罐头的挑选、分析上还是有一些经验的。今天&#xff0c;我将和大家一起探讨进口猫罐头在排行榜中是否靠前&#xff1f;同时&#xff0c;我将为大…

优化VMD全家桶!

↖加关注这种话银家怎么好意思说出口嘛-- 声明&#xff1a;对于作者的原创代码&#xff0c;禁止转售倒卖&#xff0c;违者必究&#xff01; 本期对以往智能算法优化VMD的代码做一个总结。 代码目录 优化VMD全家桶&#xff01;&#xff1a;https://mbd.pub/o/bread/ZZaVlp5xVMD为…

SAP CK51N销售订单取关联采购订单价格增强

需求fs 销售订单及行项目 enhancement 测试

微服务-我对Spring Clound的理解

官网&#xff1a;https://spring.io/projects/spring-cloud 官方说法&#xff1a;Spring Cloud 为开发人员提供了快速构建分布式系统中一些常见模式的工具&#xff08;例如配置管理、服务发现、熔断器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话…

什么是Node.js的调试器(debugger)工具?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

力扣第1035题 不相交的线中等 c++ (最长公共子序列) 动态规划 附Java代码

题目 1035. 不相交的线 中等 相关标签 数组 动态规划 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#xff0c;这些直线需要同时满足满足&#xff1a; nums1[i] nums2[j]…

uni-app前端H5页面底部内容被tabbar遮挡

问题&#xff1a; 在用uniapp写小程序的时候&#xff0c;底部有一部分内容没显示出来&#xff0c;被底部的tabbar遮挡住了 解决&#xff1a; 给最外部的view设置样式padding-bottom: var(--window-bottom)&#xff0c;如下 参考&#xff1a; 参考1 参考2 使用 uni-app 框…

idea Plugins 搜索不到插件

Settings — System Settings — HTTP Proxy&#xff0c;打开HTTP Proxy 页面&#xff0c;设置自动发现代理&#xff1a; 勾选Atuto-detect proxy settings&#xff0c;勾选Automatic proxy configuration URL&#xff0c;输入&#xff1a; https://plugins.jetbrains.com/id…

Julia绘图初步:Plots

文章目录 基础绘图绘图类型点线参数三维绘图 Julia开发环境 基础绘图 Julia中最常用的绘图模块自然是Plots&#xff0c;点击]进入安装模式后&#xff0c;输入add Plots即可安装&#xff0c;装完之后按下退格键回到Julia环境&#xff0c;就可以调用了 using Plots x 0:0.1:1…

【2023-11-09】git使用随记——gitignore文件配置某些文件忽略

git使用随记——gitignore文件配置某些文件忽略 通过git进行版本控制在项目中是非常常见的&#xff0c;一些项目构建上的文件通常是不需要进行版本控制的&#xff0c;也就无需推送到git仓库中&#xff0c;比如前端项目中的node_module目录。提供配置.gitignore文件 但是某些情…