断点下载, httpclient get请求 多线程方式
最近公司对接一个项目,有一个断点下载的接口使用,百度搜了一下,都是说使用多线程,找了一个demo, 是用URL 的方式下载的,接口定义是使用get请求,然后再请求头中放入 Range: "0-999"表示要下载的开头和结尾,返回的响应头中,Content-Range: 0-999/18845 表示此次下载文件的大小,和文件的总大小信息
因为我在第一次文件下载之前不知道文件的大小信息,所以第一次先指定1000长度的文件,然后获得总文件大小之后再开启线程进行下载,封装一个工具类,代码如下
package com.example;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;
import java.io.*;
/**
* @Auther: asus
* @Date: 2019/3/28 10 16
* @Description:
*/
public class ThreadDown {
AuthTokenItem authTokenItem = new AuthTokenItem();
/**
* 下载地址
*/
private String url;
/**
* 下载文件的保存路径
*/
private String targePath;
/**
* 线程个数
*/
private int threadCount;
/**
* 正在运行的线程数
*/
private int runningThread;
/**
* 每个线程下载文件的区块大小
*/
private long blockSize;
public TreadDown(String url, String targePath, int threadCount) {
this.url = url;
this.targePath = targePath;
this.threadCount = threadCount;
}
public void download(){
CloseableHttpClient client = HttpClients.createDefault();
CloseableHttpResponse response = null;
downThread downThread1 = null;
HttpGet method = new HttpGet(url);
// 第一次下载1000长度的文件,获取响应头中的文件总长度后再使用多线程进行下载
method.addHeader(new BasicHeader("Range", "0-999"));
try {
response = client.execute(method);
// 获取返回的响应状态码
int code = response.getStatusLine().getStatusCode();
// 文件总大小
int size = 0;
// 部分资源请求 返回状态码 206
if (code == 206){
// 这里调用的接口返回的直接是字节数组 可以直接写入流,存进文件
HttpEntity entity = response.getEntity();
byte[] bytes = EntityUtils.toByteArray(entity);
InputStream result = new ByteArrayInputStream(bytes);
BufferedInputStream in=null;
BufferedOutputStream out=null;
in=new BufferedInputStream(result);
// 保存文件到指定的文件路径下
out=new BufferedOutputStream(new FileOutputStream(targePath));
int len=-1;
byte[] b=new byte[1024];
while((len=in.read(b))!=-1){
out.write(b,0,len);
}
in.close();
out.close();
// 获取响应头中的文件大小信息 Content-Range : 0-999/18845
Header header = response.getFirstHeader("Content-Range");
String value = header.getValue();
// 拿到文件的总大小
size = Integer.parseInt(value.substring(value.lastIndexOf("/") + 1));
// 分配每个线程需要下载的长度
blockSize = (size - 999)/threadCount;
runningThread = threadCount;
}
for (int i = 1; i <= threadCount; i++) {
// 线程下载的开始位置
long startIndex = (i - 1) * blockSize + 1000;
// 线程下载的结束位置
long endIndex = i * blockSize + 999;
if (i == threadCount) {
endIndex = size - 1;
}
// 创建并开启线程
new Thread(new downThread(startIndex, endIndex, tempUrl, i)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private class downThread implements Runnable{
/**
* 下载开始位置
*/
private long startIndex;
/**
* 下载结尾位置
*/
private long endIndex;
/**
* 存放临时的文件目录
*/
private String tempUrl;
/**
* 线程id
*/
private int threadId;
public downThread(long startIndex, long endIndex, String tempUrl, int threadId) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.tempUrl = tempUrl;
this.threadId = threadId;
}
@Override
public void run() {
int total = 0;
//创建一个文件保存下载的位置
File positionFile = new File( tempUrl + threadId + ".txt");
//判断该文件是否存在
if (positionFile.exists() && positionFile.length() > 1000) {
// 得到该文件的输入流
FileInputStream fis = null;
try {
fis = new FileInputStream(positionFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
// 上次下载的总大小
int lastTotal = 0;
try {
lastTotal = Integer.valueOf(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
startIndex += lastTotal;
total += lastTotal;
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
CloseableHttpClient client = HttpClients.createDefault();
CloseableHttpResponse response;
HttpGet method = new HttpGet(url);
// 请求头中重新放入请求文件的开头和结尾位置
method.addHeader(new BasicHeader("Range", startIndex + "-" + endIndex));
int code = 0;
try {
response = client.execute(method);
code = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (code == 206){
byte[] bytes = EntityUtils.toByteArray(entity);
InputStream in = new ByteArrayInputStream(bytes);
// 创建一个新的RandomAccessFile 对象
RandomAccessFile raf = new RandomAccessFile(targePath, "rw");
// 从指定位置开始下载
raf.seek(startIndex);
int len = 0;
// 缓存区设置越大,下载速度越快
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) != -1) {
raf.write(buffer, 0, len);
//创建一个同步更新到存储设备的RandomAccessFile对象
RandomAccessFile file = new RandomAccessFile( positionFile, "rwd");
total+=len;
//必须将total转化为字节
file.write(String.valueOf(total).getBytes());
file.close();
}
raf.close();
in.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("线程" + threadId +
"下载长度" + startIndex + "--" + endIndex +
"请求code" + code);
// 线程运行完后,删除临时文件
synchronized(TreadDown.class){
runningThread--;
if(runningThread<1){
for(int i=1;i<=threadCount;i++){
File file =new File(tempUrl+i+".txt");
//删除成功,返回true
file.delete();
}
}
}
}
}
}
}
这里我们用到了一个RandomAccessFile 类,其它博客中也有比较详细的解说,简单来讲,RandomAccessFile 直接继承于Object类,可以从文件的任意位置进行操作,使用seek() 方法。
然后就是工具类方法的调用了
package com.example;
/**
* @Auther: asus
* @Date: 2019/3/28 11 50
* @Description:
*/
public class TestDownThread {
public static void main(String[] args) {
/**
* get请求的完整url, 如有参数需要先拼凑
*/
String url = "";
/**
* 存放临时文件的临时目录
*/
String tempUrl = "";
/**
* 下载文件的完整保存路径 我这里下载的是zip文件
*/
String targePath = "E:/test.zip";
/**
* 要使用的线程个数
*/
int threadCount = 5;
TreadDown td = new TreadDown(url,tempUrl,targePath,threadCount);
td.download();
}
}
执行结果
如有不当,还请指出。