先說使用Java實現斷點續傳的關鍵點:
- 本地能夠判斷當前請求下載的文件是否已經存在,或者部分存在;
- 如果存在且爲部分,則本地可以記錄下載位置,並從服務器端繼續完成下載;
- 請求的需要:
URL url = new URL("urlStr"); HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection //向服務器請求指定區間段的數據,這是實現斷點續傳的根本。 httpcon.setRequestProperty("Range", "bytes=" + startPos+ "-" + endPos);
- 保存文件的方法:採用IO包中的RandomAccessFile類:
RandomAccess downfile = new RandomAccessFile("file","rw"); Array[] startPos ; downloadfile.seek(startPos); byte[] buf = new byte[1024]; downloadfile.write(buf, 0, length);
package **********;
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:GFC
*
* 根據輸入的url和設定的線程數,來完成斷點續傳功能。
*
* 每個線程支負責某一小段的數據下載;再通過RandomAccessFile完成數據的整合。
*/
public class MultiTheradDownLoad {
private String urlStr = 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 urlStr, int threadNum) {
this.urlStr = urlStr;
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;
//設置HTTP網絡訪問代理
System.setProperty("http.proxySet", "true");
System.setProperty("http.proxyHost", "proxy3.bj.petrochina");
System.setProperty("http.proxyPort", "8080");
//從文件鏈接中獲取文件名,此處沒考慮文件名爲空的情況,此種情況可能需使用UUID來生成一個唯一數來代表文件名。
filename = urlStr.substring(urlStr.lastIndexOf('/') + 1, urlStr
.contains("?") ? urlStr.lastIndexOf('?') : urlStr.length());
tmpfilename = filename + "_tmp";
try {
//創建url
url = new URL(urlStr);
//打開下載鏈接,並且得到一個HttpURLConnection的一個對象httpcon
HttpURLConnection httpcon = (HttpURLConnection) url.openConnection();
httpcon.setRequestMethod("GET");
//獲取請求資源的總長度。
fileLength = httpcon.getContentLengthLong();
//下載文件和臨時文件
file = new File(filename);//相對目錄
tmpfile = new File(tmpfilename);
//每個線程需下載的資源大小;由於文件大小不確定,爲避免數據丟失
threadLength = fileLength%threadNum == 0 ? fileLength/threadNum : fileLength/threadNum+1;
//打印下載信息
System.out.println("fileName: " + filename + " ," + "fileLength= "
+ fileLength + " the threadLength= " + threadLength);
//各個線程在exec線程池中進行,起始位置--結束位置
if (file.exists() && file.length() == fileLength) {
System.out.println("文件已存在!!");
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("刪除臨時文件!!");
tmpfile.delete();
}
}
}
/*
* 斷點設置方法,當有臨時文件時,直接在臨時文件中讀取上次下載中斷時的斷點位置。沒有臨時文件,即第一次下載時,重新設置斷點。
*
* Rantmpfile.seek()跳轉到一個位置的目的是爲了讓各個斷點存儲的位置儘量分開。
*
* 這是實現斷點續傳的重要基礎。
*/
private void setBreakPoint(long[] startPos, long[] endPos, File tmpfile) {
RandomAccessFile rantmpfile = null;
try {
if (tmpfile.exists()) {
System.out.println("繼續下載!!");
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("線程" + id + " 開始下載!!");
while (true) {
try {
httpcon = (HttpURLConnection) task.url.openConnection();
httpcon.setRequestMethod("GET");
//防止網絡阻塞,設置指定的超時時間;單位都是ms。超過指定時間,就會拋出異常
httpcon.setReadTimeout(20000);//讀取數據的超時設置
httpcon.setConnectTimeout(20000);//連接的超時設置
if (startPos < endPos) {
//向服務器請求指定區間段的數據,這是實現斷點續傳的根本。
httpcon.setRequestProperty("Range", "bytes=" + startPos+ "-" + endPos);
System.out.println("線程 " + id+ " 長度:---- "+ (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("線程 ---" + id + " 下載完成!!");
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("線程 " + id
+ " 總下載大小: " + count);
//關閉流
is.close();
httpcon.disconnect();
downloadfile.close();
rantmpfile.close();
}
latch.countDown();//計數器自減
System.out.println("線程 " + id + " 下載完成!!");
break;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
其中設置HTTP訪問代理在公網上不需要。可以看出來,整個類中分爲三大部分:下載功能方法downloadPart();設置斷點的私有方法setBreakPoint(long[] startPos, long[] endPos, File tmpfile);以及實現下載功能的內部類,通過讀取斷點來設置向服務器請求的數據區間class DownLoadThread,其中重寫了線程的run()方法。
package Download;
public class DownLoadTest {
public static void main(String[] args) {
//http://down.sandai.net/thunder9/Thunder9.1.40.898.exe
//http://download.firefox.com.cn/releases-sha2/stub/official/zh-CN/Firefox-latest.exe
//http://p0.ifengimg.com/pmop/2017/0830/4AB0880C38002DC837856D274469E3D09BDF43B9_size28_w500_h314.jpeg
//http://img.article.pchome.net/00/22/70/23/pic_lib/wm/03.jpg
int threadNum = 4;
String filepath = "http://down.sandai.net/thunder9/Thunder9.1.40.898.exe";
MultiTheradDownLoad load = new MultiTheradDownLoad(filepath ,threadNum);
load.downloadPart();
}
}