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

欢迎关注公众号

​​​​​

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