您的位置:首页 > 编程语言 > Java开发

Java基础之多线程断点下载

2016-07-08 20:19 471 查看

基本原理

首先利用URLConnection获取想要下载的文件的长度,然后由URLConnection获取输入流,根据文件的长度以及下载线程的个数,将文件分成固定大小的块,每一块单独启动一个线程读取、写入。通过输入流读取下载文件的信息,然后将读取的信息用RandomAccessFile随机写入到本地文件中。同时,在每个线程中需要用一个临时文件来保存当前已经下载文件的长度,这样的话如果本次下载没有完成,下一次可以在原来已经下载好的基础上继续下载,就不需要重新下载原来已经下过的部分。

代码如下

如果想要多线程下载只需要传入文件下载路径,文件保存路径以及线程的个数,便可以将文件下载至本地。

package com.xqq.mutilthreaddownload;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 多线程下载
* @author xqq
*/
public class MutilThreadDownload extends Thread {

private String downloadPath;
private String savePath;
private int nThread;
private final ExecutorService exec;

public MutilThreadDownload(String downloadPath, String savePath, int nThread) {
this.downloadPath = downloadPath;
this.savePath = savePath;
this.nThread = nThread;
this.exec = Executors.newFixedThreadPool(nThread);
}

@Override
public void run() {
try {
// 连接获取文件信息
URL url = new URL(downloadPath);

HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");

int code = conn.getResponseCode();
if (code == 200) {

int length = conn.getContentLength();// 获取文件长度
System.out.println("文件的总长度为: " + length);

//每个线程所需下载文件的大小
int blockSize = length / nThread;

for (int i = 1; i <= nThread; i++) {

int startIndex = (i - 1) * blockSize;
int endIndex = i * blockSize - 1;

if (i == nThread) {
endIndex = length;
}
//开始下载文件
exec.execute(new Download(startIndex, endIndex, downloadPath, i, savePath));
}
} else {
System.out.println("文件错误......");
}

} catch (MalformedURLException e) {
System.out.println("MalformedURLException");
} catch (IOException e) {
System.out.println("IOException");
}
//当所有线程均下载完成之后,关闭线程池
exec.shutdown();
}
}


根据文件开始和结束位置从输入流中读取文件并利用RandomAccessFile保存至相应位置。

package com.xqq.mutilthreaddownload;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

public class Download implements Runnable{

private int startIndex;
private int endIndex;
private String path;
private static int runningThread = Test.nThread;
private int threadID;
private String savePath;

public Download(int startIndex, int endIndex, String path, int threadID, String savePath) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.path = path;
this.threadID = threadID;
this.savePath = savePath;
}

public void run() {

try {
File tempFile = new File(savePath + threadID + ".txt");

if(tempFile.exists() && tempFile.length() > 0){
FileInputStream fis = new FileInputStream(tempFile);
byte [] temp = new byte[1024];
int length = fis.read(temp);
String downloadLen = new String(temp, 0, length);
startIndex = Integer.parseInt(downloadLen);
fis.close();
}
System.out.println(Thread.currentThread() + " 下载范围: " + startIndex + " - >" + endIndex);
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");

int code = conn.getResponseCode();
//获取部分数据,成功返回206
if(code == 206){
RandomAccessFile file = new RandomAccessFile(savePath + "setup.exe", "rwd");
file.seek(startIndex);//定位到文件的位置

InputStream in = conn.getInputStream();

byte [] buffer = new byte[1024 * 2];
int len = 0;
int total = 0;
while((len = in.read(buffer)) != -1){
RandomAccessFile totalFile = new RandomAccessFile(savePath + threadID + ".txt", "rwd");
file.write(buffer, 0, len);
total += len;
totalFile.write((total + startIndex + "").getBytes());
totalFile.close();
}
file.close();
}else{
System.out.println(Thread.currentThread() + " 下载失败");
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
System.out.println(Thread.currentThread() + " 下载完成.....");
runningThread --;
if(runningThread == 0){
for(int i = 1; i <= Test.nThread; i++){
File file = new File(savePath + i + ".txt");
file.delete();
}
}
}
}
}


测试文件

package com.xqq.mutilthreaddownload;

public class Test {
private final static String downloadPath = "http://localhost:8080/123.exe";
private static final String savePath = "c://";
public static final int nThread = 5;

public static void main(String[] args) {
MutilThreadDownload thread = new MutilThreadDownload(downloadPath,savePath,  nThread);
thread.start();
}
}

测试结果:
文件的总长度为: 13087904
Thread[pool-1-thread-2,5,main] 下载范围: 3283180 - >5235159
Thread[pool-1-thread-3,5,main] 下载范围: 5900760 - >7852739
Thread[pool-1-thread-4,5,main] 下载范围: 8518340 - >10470319
Thread[pool-1-thread-5,5,main] 下载范围: 11133872 - >13087904
Thread[pool-1-thread-1,5,main] 下载范围: 665600 - >2617579
Thread[pool-1-thread-4,5,main] 下载完成.....
Thread[pool-1-thread-2,5,main] 下载完成.....
Thread[pool-1-thread-3,5,main] 下载完成.....
Thread[pool-1-thread-5,5,main] 下载完成.....
Thread[pool-1-thread-1,5,main] 下载完成.....


总结

我今天突然发现,使用了线程池,如果你不调用shutdown()或者shutdownNow()显示的关闭线程池,那么main函数就不会停止,会一直等待线程加入线程池中运行。知道了这个之后,我就顿悟了为什么之前我在Android开发的时候使用了线程池却一直打印线程太多的问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: