Java 多线程下载大文件(断点续传)

前段时间在项目上遇到了一个上传大文件的问题,其实如果文件比较小,很好处理;但是如果文件过大就需要考虑带宽、内存等问题了。所以很自然的就会想到将文件分块上传,最后合并,同时也支持断点续传。其实断点续传是一个很常见的功能,比如在迅雷上下载电影,中途网断了,这时候下载会暂停,待网络恢复后会继续下载。其实想实现一个完备的这种系统是很难的,本文主要是以多线程下本地大文件传输为例简单介绍一下原理(下载网络资源可以基于 HttpURLConnection 相关的 API 处理,核心代码其实是一样的),知道了主要原理后,比如实时进度条、断点续传很容易就会有实现的思路了。

主要思路就是多个线程去分块下载大文件,最后将文件合并即可。但是这里有几个要注意的地方,首先需要获取源文件的大小,这样每个线程才知道从哪里开始读,读到哪里;还有就是最后将文件合并,这个就需要多个线程按序并发写到一个文件中,就是每个线程写它读的那部分即可。并发操作方面直接使用 CompletableFuture 即可;合并文件,直接使用 RandomAccessFile 就行了。

实现代码:

package dongguabai.demo;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.stream.IntStream;

/**
 * @author Dongguabai
 * @Description
 * @Date 创建于 2020-01-26 23:49
 */
public class DownLoadProcessor {

    /**
     * 文件来源路径
     */
    private String source;

    /**
     * 目标路径
     */
    private String target;

    /**
     * 每个线程读取字节长度
     */
    private Long eachSize;

    /**
     * 读取文件总大小
     */
    private Long totalLength;

    /**
     * 源文件
     */
    private File sourceFile;

    /**
     * 目标文件
     */
    private File targetFile;

    /**
     * 并行数量
     */
    private int parallelism = 3;

    public DownLoadProcessor(String source, String target) {
        this.source = source;
        this.target = target;
    }

    public void start() throws IOException {
        sourceFile = new File(source);
        targetFile = new File(target);
        totalLength = sourceFile.length();
        RandomAccessFile raf = new RandomAccessFile(targetFile, "rw");
        //raf.setLength(totalLength);
        raf.close();

        eachSize = totalLength / parallelism;
        CompletableFuture[] completableFutures = IntStream.range(0, parallelism).boxed().map(i -> CompletableFuture
                .runAsync(() -> download(i))
                .whenComplete((result, e) -> System.out.println(i + "-> over..."))).toArray(CompletableFuture[]::new);
        CompletableFuture.allOf(completableFutures).join();
    }

    private void download(Integer index) {
        System.out.println(index);

        try (FileInputStream is = new FileInputStream(sourceFile);
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             RandomAccessFile accessFile = new RandomAccessFile(targetFile, "rw")) {

            //每次读取1024
            byte[] buff = new byte[1024];         
            //todo 待优化
            
           //获取当前线程读取区间,最后一个读取剩余部分
            int start = (int) (index * eachSize);
            int end = (int) (index == parallelism - 1 ? totalLength - 1 : (index + 1) * eachSize - 1);
            int length = end - start + 1;
            int readLength = 0;
            is.skip(start);
            int len;
          //下载文件并写入本地
            while ((len = is.read(buff)) != -1 && readLength <= length) {
                baos.write(buff, 0, len);
                readLength += len;
            }
            byte[] readData = baos.toByteArray();
            byte[] result = baos.size() > length ? Arrays.copyOf(readData, length) : readData;
            System.out.println(result.length);
            accessFile.seek(start);
            accessFile.write(result);
            System.out.println(start + "-" + end + " over.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试:

package dongguabai.demo;

import java.io.IOException;

/**
 * @author dongguabai
 * @Description
 * @Date 创建于 2020-01-26 23:42
 */
public class TestDemo {

    public static void main(String[] args) throws IOException {
        DownLoadProcessor downLoadProcessor = new DownLoadProcessor("/Users/dongguabai/Desktop/images/1234.jpg","/Users/dongguabai/Desktop/images/test3.jpg");
        downLoadProcessor.start();
    }
}

欢迎关注公众号
​​​​​​在这里插入图片描述

发布了406 篇原创文章 · 获赞 127 · 访问量 81万+

猜你喜欢

转载自blog.csdn.net/Dongguabai/article/details/104095284