前言
不含断点续传的多线程实现代码见连接
https://blog.csdn.net/m0_48396761/article/details/107734363
1. 断点续传作用
如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度。
2. 示例代码
package com.luoking.mutildownload;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class MutilDownload {
static String path = "http://i0.hdslb.com/bfs/article/4329308d201fafbb1a9c41e950988901dba4de22.jpg";
// 定义线程数量
private static final int threadCount = 3;
// 存储正在执行的线程数量
private static int runningThreadCount = 0;
public static void main(String[] args) throws Exception {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
if (conn.getResponseCode() == 200) {
// 获取服务器文件的大小
int fileLength = conn.getContentLength();
runningThreadCount = threadCount;
// 创建一个大小和要下载的大小一样的文件 提前把空间申请出来
RandomAccessFile raf = new RandomAccessFile(new File("pic.png"),
"rw");
raf.setLength(fileLength);
// 算出每个线程下载的大小
int blockSize = fileLength / threadCount;
// 计算每个线程下载的开始和结束位置
for (int i = 0; i < threadCount; i++) {
// 开始位置
int startIndex = i * blockSize;
// 结束位置
int endIndex = (i + 1) * blockSize - 1;
if (i == threadCount - 1) {
// 最后一个线程
endIndex = fileLength - 1;
}
// 开启线程去下载文件
new DownloadThread(startIndex, endIndex, i).start();
}
}
}
// 定义线程去服务器下载文件
private static class DownloadThread extends Thread {
private int startIndex;
private int endIndex;
private int threadId;
// 通过构造方法将线程的开始和结束位置传递进来
public DownloadThread(int startIndex, int endIndex, int threadId) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
}
public void run() {
try {
// 实现断点续传功能-------------------------
// 读取存储下载位置的文件
File file = new File(threadId + ".txt");
if (file.exists() && file.length() > 0) {
// 如果该文件存在并且内容的长度大于1则代表上次没有存完
BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(file)));
// 读取上一次存储的位置
int line = Integer.parseInt(br.readLine());
startIndex = line;
// 关闭流
br.close();
}
// --------------------------------------
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
// 设置一个请求头,告诉服务器每个线程下载的开始和结束位置
conn.setRequestProperty("Range", "bytes=" + startIndex + "-"
+ endIndex);
// 获取返回的状态码
// 200代表获取服务器全部资源成功 206代表获取服务器部分资源成功
if (conn.getResponseCode() == 206) {
// 创建随机读写文件对象
RandomAccessFile raf = new RandomAccessFile(new File(
"pic.png"), "rw");
// 设置每个线程从自己的位置开始写
raf.seek(startIndex);
// 获取服务器返回的资源
InputStream in = conn.getInputStream();
// 当前线程下载的大小
int total = 0;
// 把获取到的资源写入文件中
int len = -1;
// 定义 1MB 个字节的缓冲区
byte[] buffer = new byte[1024 * 1024];
while ((len = in.read(buffer)) != -1) {
raf.write(buffer, 0, len);
// 断点续传:记录下载的位置--------------------
total += len;
// 把当前下载的位置存储下来,下次再下载的时候就可以按照上一次下载的位置继续下载
int currentThreadPosition = total + startIndex;
// rwd:直接写入底层存储设备(硬盘),不存放到硬盘缓存区中
RandomAccessFile positionRaf = new RandomAccessFile(
threadId + ".txt", "rwd");
positionRaf.write(String.valueOf(currentThreadPosition)
.getBytes());
positionRaf.close();
// ------------------------------------------
}
// 关闭随机读写文件流 释放资源
raf.close();
// 下载完毕输出信息
System.out.println("下载完毕 --- 线程id: " + threadId);
//-----------------------------------------------
// 下载完毕后,删除记录下载位置的txt文件
synchronized (DownloadThread.class) {
runningThreadCount--;
if (runningThreadCount == 0) {
for (int i = 0; i < threadCount; i++) {
new File(i + ".txt").delete();
}
}
}
//-------------------------------------------------
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}