原理
先获取文件大小,然后分段分配任务给线程下载
在开始多线程下载前得先得知下载文件的大小,如果在之前的流程中并没有告知文件大小则可以使用HTTP请求方法 HEAD,这个请求方法类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头,在头部中可以找到字段content-length就是文件大小了。
得知文件长度后应分割需要下载的起止位置以便之后使用。
有具体位置后就可以启动多线程发送网络请求,在网络请求中使用HTTP协议的头部标志Range,这个标志的使用方法是Range:bytes=start-end。
实现
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* Created with IntelliJ IDEA.
*
* @Auther: zlf
* @Date: 2021/09/14/1:01
* @Description:
*/
public class MutiThreadDownload {
private int threadCount = 5; // 下载线程数
private long blocksize; // 每线程下载区块大小
private int runningThreadCount; // 正运行线程数
public int getThreadCount() {
return threadCount;
}
public void setThreadCount(int threadCount) {
this.threadCount = threadCount;
}
public long getBlocksize() {
return blocksize;
}
public void setBlocksize(long blocksize) {
this.blocksize = blocksize;
}
public int getRunningThreadCount() {
return runningThreadCount;
}
public void setRunningThreadCount(int runningThreadCount) {
this.runningThreadCount = runningThreadCount;
}
/**
* @Description: 多线程分段下载
* @Param: [url, filePath]
* @return: void
* @Author: zlf
* @Date: 2021/9/14
*/
public void download(String url, String filePath) throws IOException {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
int code = conn.getResponseCode();
if (code == 200) {
long size = conn.getContentLength();// 文件大小
blocksize = size / threadCount;
// 1.创建RandomAccessFile文件(可以随机访问)
RandomAccessFile raf = new RandomAccessFile(new File(filePath), "rw");
raf.setLength(size);
// 2.多线程下载对应区块
runningThreadCount = threadCount;
for (int i = 1; i <= threadCount; i++) {
long startIndex = (i - 1) * blocksize;
long endIndex = i * blocksize - 1;
if (i == threadCount) {
endIndex = size - 1;
}
new DownloadThread(i, url, filePath,startIndex, endIndex).start();
}
}
conn.disconnect();
}
/***
* @Description: 分段下载的线程
* @Param:
* @return:
* @Author: zlf
* @Date: 2021/9/14
*/
private class DownloadThread extends Thread {
private int id;
private String url;
private long startIndex;
private long endIndex;
private String filePath;
public DownloadThread(int id, String url, String filePath, long startIndex, long endIndex) {
this.id = id;
this.url = url;
this.startIndex = startIndex;
this.filePath = filePath;
this.endIndex = endIndex;
}
@Override
public void run() {
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
// 1.读取文件断点位置curIndex
int curIndex = 0;
File indexFile = new File("indexFile_"+id);
if (indexFile.exists() && indexFile.length() > 0) {
FileInputStream fis = new FileInputStream(indexFile);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
curIndex = Integer.valueOf(br.readLine());
startIndex += curIndex;
fis.close();
}
// 2.从startIndex位置下载文件, 并从startIndex位置写入raf文件
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
InputStream is = conn.getInputStream();
RandomAccessFile raf = new RandomAccessFile(new File(filePath), "rw");
raf.seek(startIndex);
int len = 0;
byte[] buffer = new byte[1024*1024];
while ((len = is.read(buffer)) != -1) {
raf.write(buffer, 0, len);
// 更新断点位置
RandomAccessFile indexRaf = new RandomAccessFile(indexFile, "rwd");
curIndex += len;
indexRaf.write(String.valueOf(curIndex).getBytes());
indexRaf.close();
}
is.close();
raf.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 3.所有线程下载完,删除断点位置文件indexFile
synchronized (MutiThreadDownload.class){
if (--runningThreadCount == 0) {
for (int i = 1; i <= threadCount; i++) new File("indexFile_"+i).delete();
}
}
}
}
}
public static void main(String[] args) throws IOException {
MutiThreadDownload mutiThreadDownload = new MutiThreadDownload();
mutiThreadDownload.setRunningThreadCount(5);
mutiThreadDownload.download("http://img.netbian.com/file/2020/0904/de2f77ed1090735b441ba5e4c2b460ca.jpg","D:/a.jpg");
}
}