如何使用Java多线程下载网络文件,并实现断点续传
在现代网络应用中,多线程下载是一种常见的技术,它可以显著提高下载速度并提供更好的用户体验。本篇文章将介绍如何使用Java实现多线程下载,并结合项目中的代码作为示例进行讲解。
1. 多线程下载的基本原理
多线程下载的基本思想是将一个文件分成多个部分,每个部分由一个线程独立下载,最后将这些部分合并成完整的文件。这样可以充分利用带宽和计算资源,提高下载速度。
使用Http请求头的Range字段可以实现文件的分段下载,服务器会根据Range字段返回指定范围的文件内容。例如,请求头Range: bytes=0-1023表示获取文件的前1024字节。
断点续传是多线程下载的一个重要功能,它可以在下载中断后继续从中断的地方继续下载,避免重新下载整个文件。断点续传的实现方法是在下载过程中保存下载进度,例如保存已下载的字节数,以便在下次下载时继续下载。
注意⚠️: 后续示例代码为了方便阅读,省略细节处理,只贴出核心代码。完整代码请参考文章最后给的项目地址。
2. 创建下载器类
首先,我们需要创建一个下载器类,用于管理下载任务。以下是项目中的Downloader类的基本框架:
```Java
public class Downloader {
private String url;
private String fileName;
private int threadCount;
private long fileSize;
private List 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来实现:
```Java
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类的基本实现:
```Java
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类中,我们需要根据文件大小和线程数量来启动多个下载线程:
```Java
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. 检查文件是否已经下载
在开始下载之前,需要检查目标文件是否已经存在。如果文件已经存在且下载未完成,则继续下载:
```java
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. 设置文件大小和文件名
获取文件的大小和文件名,并设置目标文件的地址:
```java
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. 等待下载完成并获取下载结果
使用多线程下载文件,并等待所有线程完成下载。期间可以保存临时文件以记录下载进度:
```java
private boolean waitDownAndGetResult(List> 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 项目地址