用java實現檔案的斷點續傳併發下載
阿新 • • 發佈:2019-01-08
說明
用java實現檔案的斷點續傳,使用了HTTP的首部欄位實現,在網上看到例子,手動實現一遍,理解其原理,在這記錄下
正文
要實現斷點續傳,要在請求中設定請求開始的位置和結束位置,在HTTP請求中設定RANGE首部欄位,之後伺服器如果能正常返回,返回206狀態碼
用java實現的關鍵點:
1.設定請求的首部欄位,使用java的net包
2.在讀取資原始檔後,要儲存檔案,從斷點處儲存,使用RandAccessFile類
3.使用多執行緒併發的方式進行,如何正確設定起始位置
主要思路就是:
1. 設定檔案資訊,包括檔案所在的URL,檔名,檔案儲存的路徑及檔案需要分段下載的次數
2. 下載時,先連線伺服器,得到檔案的大小,通過伺服器響應的首部欄位Content-Length獲得,得到檔案大小後,根據分段下載的次數設定每次開始的位置,結束位置。並創造一個資訊臨時檔案,用來儲存每次分段下載的起始位置,用於非第一次下載時,可以直接本地讀取起始資訊
3. 分段下載根據開始位置,儲存在下載檔案的合適位置,使用RandAccessFile類的seek()方法定位
4.
1、設定首部欄位
在分段抓取的類中,根據分段次數,設定開始位置
URL ourl = new URL(url);
HttpURLConnection httpConnection = (HttpURLConnection) ourl.openConnection();
String prop = "bytes=" + startPos + "-";
httpConnection.setRequestProperty("RANGE", prop); //設定請求首部欄位 RANGE
分段抓取程式碼
package action;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import util.FileUtil;
import util.LogUtil;
/**
* 用於分段傳輸
* 使用HTTP協議的首部欄位實現
* @author wds
*
*/
public class FileSplitFetch implements Runnable{
protected String url; // 檔案所在url
protected long startPos; // 分段傳輸的開始位置
protected long endPos; // 結束位置
protected int threadID; // 執行緒編號
protected boolean downOver = false; // 下載完成標誌
protected boolean stop = false; // 當前分段結束標誌
FileUtil fileUtil = null; // 檔案工具
public FileSplitFetch(String url, long startPos, long endPos, int threadID, String fileName) throws IOException {
super();
this.url = url;
this.startPos = startPos;
this.endPos = endPos;
this.threadID = threadID;
fileUtil = new FileUtil(fileName, startPos);
}
@Override
public void run() {
while(startPos < endPos && !stop){
try {
URL ourl = new URL(url);
HttpURLConnection httpConnection = (HttpURLConnection) ourl.openConnection();
String prop = "bytes=" + startPos + "-";
httpConnection.setRequestProperty("RANGE", prop); //設定請求首部欄位 RANGE
LogUtil.log(prop);
InputStream input = httpConnection.getInputStream();
byte[] b = new byte[1024];
int bytes = 0;
while((bytes = input.read(b)) > 0 && startPos < endPos && !stop){
startPos += fileUtil.write(b, 0, bytes);
}
LogUtil.log("Thread" + threadID + " is done");
downOver = true;
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 列印響應的頭部資訊
* @param conn
*/
public void printResponseHeader(HttpURLConnection conn){
for(int i = 0; ; i++){
String fieldsName = conn.getHeaderFieldKey(i);
if(fieldsName != null){
LogUtil.log(fieldsName + ":" + conn.getHeaderField(fieldsName));
}else{
break;
}
}
}
/**
* 停止分段傳輸
*/
public void setSplitTransStop(){
stop = true;
}
}
2、檔案的儲存
在檔案儲存時,要根據開始位置儲存在下載檔案的適當位置,使用RandomAccessFile的seek()方法
public class FileUtil{
private RandomAccessFile file;
private long startPos; // 檔案儲存的起始位置
public FileUtil(String fileName, long startPos) throws IOException{
file = new RandomAccessFile(fileName, "rw");
this.startPos = startPos;
file.seek(startPos);
}
public synchronized int write(byte[] data, int start, int len){
int res = -1;
try {
file.write(data, start, len);
res = len;
} catch (IOException e) {
LogUtil.log(e.getMessage());
e.printStackTrace();
}
return res;
}
}
3、併發下載
在下載檔案時,可以使用多個子執行緒併發進行,這裡使用了一個數組儲存多個執行緒
/**
* 開始下載檔案
* 1. 獲取檔案長度
* 2. 分割檔案
* 3. 例項化分段下載子執行緒
* 4. 啟動子執行緒
* 5. 等待子執行緒的返回
* @throws IOException
*/
public void startDown(){
if(firstDown){
fileLen = getFileSize();
if(fileLen == -1){
LogUtil.log("檔案大小未知");
return;
}else if(fileLen == -2){
LogUtil.log("檔案不可訪問");
return;
}
else{
// 設定每次分段下載的開始位置
for(int i = 0; i < startPos.length; i++){
startPos[i] = i * (fileLen / startPos.length);
}
//設定每次分段下載的結束位置
for(int i = 0; i < endPos.length - 1; i++){
endPos[i] = startPos[i + 1];
}
endPos[endPos.length - 1] = fileLen;
}
}
//啟動分段下載子執行緒
try {
fileSplitFetchs = new FileSplitFetch[startPos.length];
for(int i = 0; i < startPos.length; i++){
System.out.println(startPos[i] + " " + endPos[i]);
fileSplitFetchs[i] = new FileSplitFetch(siteInfo.getUrl(), startPos[i], endPos[i], i,
siteInfo.getFilePath() + File.separator + siteInfo.getFileName());
LogUtil.log("Threa " + i + ", start= " + startPos[i] + ", end= " + endPos[i]);
new Thread(fileSplitFetchs[i]).start();
}
//儲存檔案下載資訊
saveInfo();
//迴圈判斷所有檔案是否下載完畢
boolean breakWhile = false;
while(!stop){
LogUtil.sleep(500);
breakWhile = true;
for(int i = 0; i < startPos.length; i++){
if(! fileSplitFetchs[i].downOver){
breakWhile = false; // 還存在未下載完成的執行緒
break;
}
}
if(breakWhile)
break;
}
} catch (IOException e) {
LogUtil.log(e.getMessage());
e.printStackTrace();
}
LogUtil.log("檔案下載完成");
}
4、設定檔案的基本資訊
基本資訊包含了檔案所在站點資訊,檔案本地儲存路徑,檔名,檔案分段下載次數
public class SiteInfo implements Serializable {
private static final int SPLIT_COUNT = 5; // 預設次數為5次
private String url; // 檔案所在站點的url
private String filePath; // 檔案儲存的路徑
private String fileName; // 檔案的名字
private int splits; // 分段下載檔案的次數
public SiteInfo(){
this("","","",SPLIT_COUNT);
}
public SiteInfo(String url, String filePath, String fileName, int splits) {
this.url = url;
this.filePath = filePath;
this.fileName = fileName;
this.splits = splits;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public int getSplits() {
return splits;
}
public void setSplits(int splits) {
this.splits = splits;
}
public String getSimpleName(){
String[] names = fileName.split("\\.");
return names[0];
}
}