import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* Encode:UTF-8
*
* Author:zhiming.xu
*
* 多線程的斷點下載程序,根據輸入的url和指定線程數,來完成斷點續傳功能。
*
* 每一個線程支負責某一小段的數據下載;再經過RandomAccessFile完成數據的整合。
*/
public class MultiTheradDownLoad {
private String filepath = null;
private String filename = null;
private String tmpfilename = null;
private int threadNum = 0;
private CountDownLatch latch = null;//設置一個計數器,代碼內主要用來完成對緩存文件的刪除
private long fileLength = 0l;
private long threadLength = 0l;
private long[] startPos;//保留每一個線程下載數據的起始位置。
private long[] endPos;//保留每一個線程下載數據的截止位置。
private boolean bool = false;
private URL url = null;
//有參構造函數,先構造須要的數據
public MultiTheradDownLoad(String filepath, int threadNum) {
this.filepath = filepath;
this.threadNum = threadNum;
startPos = new long[this.threadNum];
endPos = new long[this.threadNum];
latch = new CountDownLatch(this.threadNum);
}
/*
* 組織斷點續傳功能的方法
*/
public void downloadPart() {
File file = null;
File tmpfile = null;
HttpURLConnection httpcon = null;
//在請求url內獲取文件資源的名稱;此處沒考慮文件名爲空的狀況,此種狀況可能需使用UUID來生成一個惟一數來表明文件名。
filename = filepath.substring(filepath.lastIndexOf('/') + 1, filepath
.contains("?") ? filepath.lastIndexOf('?') : filepath.length());
tmpfilename = filename + "_tmp";
try {
url = new URL(filepath);
httpcon = (HttpURLConnection) url.openConnection();
setHeader(httpcon);
fileLength = httpcon.getContentLengthLong();//獲取請求資源的總長度。
file = new File(filename);
tmpfile = new File(tmpfilename);
threadLength = fileLength / threadNum;//每一個線程需下載的資源大小。
System.out.println("fileName: " + filename + " ," + "fileLength= "
+ fileLength + " the threadLength= " + threadLength);
if (file.exists() && file.length() == fileLength) {
System.out
.println("the file you want to download has exited!!");
return;
} else {
setBreakPoint(startPos, endPos, tmpfile);
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < threadNum; i++) {
exec.execute(new DownLoadThread(startPos[i], endPos[i],
this, i, tmpfile, latch));
}
latch.await();//當你的計數器減爲0以前,會在此處一直阻塞。
exec.shutdown();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (file.length() == fileLength) {
if (tmpfile.exists()) {
System.out.println("delect the temp file!!");
tmpfile.delete();
}
}
}
/*
* 斷點設置方法,當有臨時文件時,直接在臨時文件中讀取上次下載中斷時的斷點位置。沒有臨時文件,即第一次下載時,從新設置斷點。
*
* rantmpfile.seek()跳轉到一個位置的目的是爲了讓各個斷點存儲的位置儘可能分開。
*
* 這是實現斷點續傳的重要基礎。
*/
private void setBreakPoint(long[] startPos, long[] endPos, File tmpfile) {
RandomAccessFile rantmpfile = null;
try {
if (tmpfile.exists()) {
System.out.println("the download has continued!!");
rantmpfile = new RandomAccessFile(tmpfile, "rw");
for (int i = 0; i < threadNum; i++) {
rantmpfile.seek(8 * i + 8);
startPos[i] = rantmpfile.readLong();
rantmpfile.seek(8 * (i + 1000) + 16);
endPos[i] = rantmpfile.readLong();
System.out.println("the Array content in the exit file: ");
System.out.println("thre thread" + (i + 1) + " startPos:"
+ startPos[i] + ", endPos: " + endPos[i]);
}
} else {
System.out.println("the tmpfile is not available!!");
rantmpfile = new RandomAccessFile(tmpfile, "rw");
//最後一個線程的截止位置大小爲請求資源的大小
for (int i = 0; i < threadNum; i++) {
startPos[i] = threadLength * i;
if (i == threadNum - 1) {
endPos[i] = fileLength;
} else {
endPos[i] = threadLength * (i + 1) - 1;
}
rantmpfile.seek(8 * i + 8);
rantmpfile.writeLong(startPos[i]);
rantmpfile.seek(8 * (i + 1000) + 16);
rantmpfile.writeLong(endPos[i]);
System.out.println("the Array content: ");
System.out.println("thre thread" + (i + 1) + " startPos:"
+ startPos[i] + ", endPos: " + endPos[i]);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (rantmpfile != null) {
rantmpfile.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
* 實現下載功能的內部類,經過讀取斷點來設置向服務器請求的數據區間。
*/
class DownLoadThread implements Runnable {
private long startPos;
private long endPos;
private MultiTheradDownLoad task = null;
private RandomAccessFile downloadfile = null;
private int id;
private File tmpfile = null;
private RandomAccessFile rantmpfile = null;
private CountDownLatch latch = null;
public DownLoadThread(long startPos, long endPos,
MultiTheradDownLoad task, int id, File tmpfile,
CountDownLatch latch) {
this.startPos = startPos;
this.endPos = endPos;
this.task = task;
this.tmpfile = tmpfile;
try {
this.downloadfile = new RandomAccessFile(this.task.filename,
"rw");
this.rantmpfile = new RandomAccessFile(this.tmpfile, "rw");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
this.id = id;
this.latch = latch;
}
@Override
public void run() {
HttpURLConnection httpcon = null;
InputStream is = null;
int length = 0;
System.out.println("the thread " + id + " has started!!");
while (true) {
try {
httpcon = (HttpURLConnection) task.url.openConnection();
setHeader(httpcon);
//防止網絡阻塞,設置指定的超時時間;單位都是ms。超過指定時間,就會拋出異常
httpcon.setReadTimeout(20000);//讀取數據的超時設置
httpcon.setConnectTimeout(20000);//鏈接的超時設置
if (startPos < endPos) {
//向服務器請求指定區間段的數據,這是實現斷點續傳的根本。
httpcon.setRequestProperty("Range", "bytes=" + startPos
+ "-" + endPos);
System.out
.println("Thread " + id
+ " the total size:---- "
+ (endPos - startPos));
downloadfile.seek(startPos);
if (httpcon.getResponseCode() != HttpURLConnection.HTTP_OK
&& httpcon.getResponseCode() != HttpURLConnection.HTTP_PARTIAL) {
this.task.bool = true;
httpcon.disconnect();
downloadfile.close();
System.out.println("the thread ---" + id
+ " has done!!");
latch.countDown();//計數器自減
break;
}
is = httpcon.getInputStream();//獲取服務器返回的資源流
long count = 0l;
byte[] buf = new byte[1024];
while (!this.task.bool && (length = is.read(buf)) != -1) {
count += length;
downloadfile.write(buf, 0, length);
//不斷更新每一個線程下載資源的起始位置,並寫入臨時文件;爲斷點續傳作準備
startPos += length;
rantmpfile.seek(8 * id + 8);
rantmpfile.writeLong(startPos);
}
System.out.println("the thread " + id
+ " total load count: " + count);
//關閉流
is.close();
httpcon.disconnect();
downloadfile.close();
rantmpfile.close();
}
latch.countDown();//計數器自減
System.out.println("the thread " + id + " has done!!");
break;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/*
* 爲一個HttpURLConnection模擬請求頭,假裝成一個瀏覽器發出的請求
*/
private void setHeader(HttpURLConnection con) {
con.setRequestProperty(
"User-Agent",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/8.04 (hardy) Firefox/3.0.3");
con.setRequestProperty("Accept-Language", "en-us,en;q=0.7,zh-cn;q=0.3");
con.setRequestProperty("Accept-Encoding", "aa");
con.setRequestProperty("Accept-Charset",
"ISO-8859-1,utf-8;q=0.7,*;q=0.7");
con.setRequestProperty("Keep-Alive", "300");
con.setRequestProperty("Connection", "keep-alive");
con.setRequestProperty("If-Modified-Since",
"Fri, 02 Jan 2009 17:00:05 GMT");
con.setRequestProperty("If-None-Match", "\"1261d8-4290-df64d224\"");
con.setRequestProperty("Cache-Control", "max-age=0");
con.setRequestProperty("Referer",
"http://www.skycn.com/soft/14857.html");
}
}html
============================================================================java
public class DownLoadTest {
/**
* @param args
*/
public static void main(String[] args) {
String filepath = "http://127.0.0.1:8080/file/loadfile.mkv";
MultiTheradDownLoad load = new MultiTheradDownLoad(filepath ,4);
load.downloadPart();
}
}瀏覽器