首页 > 编程语言 >【Java多线程】如何使用Java多线程下载网络文件 断点续传

【Java多线程】如何使用Java多线程下载网络文件 断点续传

时间:2025-01-09 17:56:13浏览次数:1  
标签:断点续传 Java url private fileName 下载 多线程 conn

如何使用Java多线程下载网络文件,并实现断点续传

在现代网络应用中,多线程下载是一种常见的技术,它可以显著提高下载速度并提供更好的用户体验。本篇文章将介绍如何使用Java实现多线程下载,并结合项目中的代码作为示例进行讲解。

1. 多线程下载的基本原理

多线程下载的基本思想是将一个文件分成多个部分,每个部分由一个线程独立下载,最后将这些部分合并成完整的文件。这样可以充分利用带宽和计算资源,提高下载速度。
使用Http请求头的Range字段可以实现文件的分段下载,服务器会根据Range字段返回指定范围的文件内容。例如,请求头Range: bytes=0-1023表示获取文件的前1024字节。

断点续传是多线程下载的一个重要功能,它可以在下载中断后继续从中断的地方继续下载,避免重新下载整个文件。断点续传的实现方法是在下载过程中保存下载进度,例如保存已下载的字节数,以便在下次下载时继续下载。

注意⚠️: 后续示例代码为了方便阅读,省略细节处理,只贴出核心代码。完整代码请参考文章最后给的项目地址。

2. 创建下载器类

首先,我们需要创建一个下载器类,用于管理下载任务。以下是项目中的Downloader类的基本框架:

public class Downloader {
    private String url;
    private String fileName;
    private int threadCount;
    private long fileSize;
    private List<DownloadThread> threads = new ArrayList<>();

    public Downloader(String url, String fileName, int threadCount) {
        this.url = url;
        this.fileName = fileName;
        this.threadCount = threadCount;
    }

    public void download() throws Exception {
        // 省略具体实现
    }

    // 其他方法
}

3. 获取文件大小

在开始下载之前,需要获取文件的大小,以便确定每个线程下载的范围。可以使用HttpURLConnection来实现:

public void getFileSize() throws IOException {
    URL url = new URL(this.url);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("HEAD");
    this.fileSize = conn.getContentLengthLong();
    conn.disconnect();
}

4. 创建下载线程

接下来,我们需要创建下载线程,每个线程负责下载文件的一部分。以下是DownloadThread类的基本实现:

public class DownloadThread extends Thread {
    private String url;
    private String fileName;
    private long start;
    private long end;

    public DownloadThread(String url, String fileName, long start, long end) {
        this.url = url;
        this.fileName = fileName;
        this.start = start;
        this.end = end;
    }

    @Override
    public void run() {
        try {
            URL url = new URL(this.url);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 主要是这一行代码,设置Range头部信息,告诉服务器要获取文件的哪一部分
            conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
            InputStream in = conn.getInputStream();
            RandomAccessFile raf = new RandomAccessFile(this.fileName, "rw");
            raf.seek(start);
            byte[] buffer = new byte[1024];
            int len;
            while ((len = in.read(buffer)) != -1) {
                raf.write(buffer, 0, len);
            }
            raf.close();
            in.close();
            conn.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5. 启动下载线程

在Downloader类中,我们需要根据文件大小和线程数量来启动多个下载线程:

public void download() throws Exception {
    getFileSize();
    long partSize = fileSize / threadCount;
    for (int i = 0; i < threadCount; i++) {
        long start = i * partSize;
        long end = (i == threadCount - 1) ? fileSize - 1 : (i + 1) * partSize - 1;
        DownloadThread thread = new DownloadThread(url, fileName, start, end);
        threads.add(thread);
        thread.start();
    }

    for (DownloadThread thread : threads) {
        thread.join();
    }
}

6. 断点续传

在文件下载过程中,断点续传功能非常重要。它可以在下载中断后(例如暂停或网络中断)继续从中断的地方继续下载,避免重新下载整个文件。以下是项目中实现断点续传的关键代码片段。

1. 检查文件是否已经下载

在开始下载之前,需要检查目标文件是否已经存在。如果文件已经存在且下载未完成,则继续下载:

private void checkFile(File target, File tempFile) throws StoppedException {
    if (target.exists()) {
        if (!tempFile.exists()) {
            System.out.println(target.getAbsoluteFile().getPath() + "文件已经存在,是否覆盖 ? y/n ");
            var scanner = new Scanner(System.in);
            var s = scanner.next();
            if (!Objects.equals("y", s)) {
                throw new StoppedException();
            }
            return;
        }
        System.out.println(target.getAbsoluteFile().getPath() + "文件存在,下载未完成,继续下载");
    }
}

2. 设置文件大小和文件名

获取文件的大小和文件名,并设置目标文件的地址:

private void setTotalAndFileName(DownFileBO downFileBO) throws IOException {
    URL url = new URL(downFileBO.getUrl());
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    conn.connect();
    int contentLength = conn.getContentLength();
    downFileBO.setTotalSize(contentLength);
    total = contentLength;

    String fileName = "";
    String contentDisposition = conn.getHeaderField("Content-Disposition");
    if (contentDisposition != null && contentDisposition.indexOf("=") != -1) {
        fileName = contentDisposition.split("=")[1];
    } else {
        fileName = url.getPath().substring(url.getPath().lastIndexOf("/") + 1);
    }
    downFileBO.setFileName(fileName);
    var targetFolder = FileUtil.getTargetFolder(downFileBO.getTargetLocalPath());
    downFileBO.setTargetLocalPath(targetFolder);

    conn.disconnect();
}

3. 等待下载完成并获取下载结果

使用多线程下载文件,并等待所有线程完成下载。期间可以保存临时文件以记录下载进度:

private boolean waitDownAndGetResult(List<Future<Boolean>> future, DownFileBO downFileBO,
    RandomAccessFile tempRandomAccessFile) throws IOException, InterruptedException {
    while (true) {
        var finish = future.stream().allMatch(Future::isDone);
        if (finish) {
            break;
        }
        saveTempFile(tempRandomAccessFile, downFileBO);
        FileUtil.printLog(total, progressSize.get());
        Thread.sleep(500 * 1);
    }
    return future.stream().allMatch(futureItem -> {
        try {
            return futureItem.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    });
}

private void saveTempFile(RandomAccessFile tempRandomAccessFile, DownFileBO downFileBO)
    throws IOException {
    var jsonString = JSON.toJSONString(downFileBO);
    var length = jsonString.length();
    var tempStr = length + TEMP_LEN_FLAG + jsonString;
    tempRandomAccessFile.seek(0);
    tempRandomAccessFile.write(tempStr.getBytes());
}

结语
通过以上步骤,我们实现了一个简单的Java多线程下载器。你可以根据实际需求进行扩展和优化,例如添加下载进度显示、错误处理等功能。

希望这篇文章对你有所帮助!如果你觉得这个文章有用,帮忙点赞、收藏,谢谢!

需要源码的可以去这里clone 项目地址

标签:断点续传,Java,url,private,fileName,下载,多线程,conn
From: https://www.cnblogs.com/seazhan/p/18662625

相关文章

  • Java类加载机制
    类加载机制类加载机制类加载的过程加载(Loading)验证(Verification)准备(Preparation)解析(Resolution)初始化(Initialization)类加载器启动类加载器扩展类加载器应用程序类加载器双亲委派模型类加载机制JDK的编译器javac负责将java文件编译成class字节码文件java负责启动j......
  • 基于Java+Springboot+MySQL校园教室预约系统设计与实现
     博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育、辅导。所有项目都配有从入门到精通的基础知识视频课程,学习后应对毕业设计答辩,提供核心代码讲解,答辩指导。项目配有对应开发......
  • 【Java】备忘录
    创建项目VSCode安装ExtensionPackforJava插件查看命令面板(Shift+Command+P)->输入CreateJavaProject->Nobuildtools->选择项目位置->输入项目名称项目结构java├──.vscode├──bin#编译后产生├──lib├──src│└──Ap......
  • 【Javascript Day4】三元运算符及循环(while、do while)
    目录三元运算符循环while循环do{ }while()循环案例三元运算符//0:女 1:男     varsex=1;    //编号的转换变量,基于编号的值提供显示文本    vartemp="";    if(sex===0){      temp="女";   ......
  • JavaScript 正则
    一:正则概念       用于定义字符串规则,并检查字符串是否符合规则,合规的内容提取出来    二:正则创建方法方法1:构造函数            var变量=newRegExp('正则表达式','匹配模式');            参1: 规则  ......
  • org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException
    1、问题概述将一个springboot项目打成Jar包后,在本地使用java-jar命令启动服务,服务能启动成功,但是会有如下报错信息。说明:配置文件为外置配置文件,与jar处于同目录下启动命令如下:java-jarblade-gateway.jar--spring.config.location=application-dev.yml--serve......
  • java入门与基础语法
    java入门三高问题:高可用,高性能,高并发Java特性与优势:简单性,面向对象,可移植性,高性能,分布式,动态性,多线程,安全性,健壮性Java三大版本Javase(标准版),javame(嵌入式开发),javaee(企业级开发)jdk:java开发者工具包jre:java运行环境jvm:java虚拟机安装java环境(自行搜索网上其他博......
  • 身份证实名认证接口核验显示库无原因?Java身份认证API
    随着信息技术的日新月异,互联网已经深深融入了我们生活的方方面面。从购物、娱乐到工作学习,互联网平台的便捷性为我们的生活带来了前所未有的改变。然而,伴随着这种迅猛发展的是一些不可忽视的安全隐患。为了构建更加健康、安全、可信的网络环境,越来越多的互联网平台开始推行......
  • 【JavaScript编程】并行运行Promise
    举个例子,如果现在我们想要获取三个国家的基本信息,但是这个顺序是无所谓的;按照我们之前的学习的异步代码,如下:constget3Countries=asyncfunction(c1,c2,c3){try{const[data1]=awaitgetJSON(`https://restcountries.com/v2/name/${c1}`);const[data2]=......
  • 【JAVA编程】通过自定义注解与AOP防止接口重复提交实战
    引言在Web应用开发中,特别是在处理表单提交或API调用时,可能会遇到用户因网络延迟、按钮多次点击等原因导致的重复提交问题。为了解决这一问题,通常的做法是在前端禁用提交按钮,或者在后端使用唯一令牌(Token)机制来确保请求的唯一性。然而,这些方法往往需要针对每个可能的重复提交场景......