基於HTTP的多執行緒檔案下載功能實現
阿新 • • 發佈:2019-01-23
思想
- 檔案資訊獲取的獲取方式與單執行緒的方式一樣
- 與單執行緒相比不同的是將遠端檔案分塊併發獲取,然後再併發寫入到本地暫存檔案中
- 遠端檔案分塊的實現依據是:connection.setRequestProperty(“Range”,”bytes=”+start+”-“+end)
- 本地將檔案寫入指定位置的實現依據是:RandomAccessFile
程式碼實現
package org.hanmeis;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;
/**
* Created by zhao.wu on 2016/12/7.
* 多執行緒下載器
* 1. 使用{@see RandomAccessFile}在檔案任意位置寫入內容
* 2. 使用 {@code connection.setRequestProperty("Range","bytes="+start+"-"+end);}設定從伺服器上讀取的檔案塊。
*/
public class MuiltDownloader {
private static Logger logger = Logger.getLogger("MuiltDownloader");
public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
String url = "http://dlsw.baidu.com/sw-search-sp/soft/e7/40642/Git-2.7.2-64-bit_setup.1457942968.exe" ;
MuiltDownloader muiltDownloader = new MuiltDownloader(url, 10);
muiltDownloader.connectServer();
muiltDownloader.startDownload();
}
private AtomicLong currentSize = new AtomicLong(0l);
private long preSize;
private long fileSize;
private String realFileName;
private String tempFileName;
private String urlStr;
private int taskNum;
private Timer timer = new Timer();
private MuiltDownloader(String urlStr, int taskNum) {
logger.info("新任務加入下載佇列......");
this.urlStr = urlStr;
this.taskNum = taskNum;
this.tempFileName = UUID.randomUUID().toString().replace("-", "");
}
private void connectServer() throws IOException {
URL url = new URL(urlStr);
logger.info(String.format("連線伺服器:%s://%s",url.getProtocol(),url.getHost()));
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(10 * 1000);
this.fileSize = Long.parseLong(connection.getHeaderField("Content-Length"));
String[] temp = url.getFile().split("/");
if (temp.length != 0) {
this.realFileName = temp[temp.length - 1];
}
logger.info(String.format("檔名:%s 大小:%.2fM", this.realFileName, this.fileSize*1.0/1024/1024));
RandomAccessFile randomAccessFile = new RandomAccessFile(tempFileName,"rw");
randomAccessFile.setLength(fileSize);
randomAccessFile.close();
}
private void startDownload() throws InterruptedException, ExecutionException {
long foot = fileSize / taskNum;
long rest = fileSize % taskNum;
List<BlockDownLoadTask> tasks = new ArrayList<>();
for (int i = 0; i < taskNum; i++) {
logger.info(String.format("初始化第%d個下載執行緒", i+1));
BlockDownLoadTask task;
if (i < taskNum - 1) {
task = new BlockDownLoadTask(tempFileName, urlStr, i * foot, (i + 1) * foot - 1, currentSize);
} else {
task = new BlockDownLoadTask(tempFileName, urlStr, i * foot, fileSize, currentSize);
}
tasks.add(task);
}
logger.info(String.format("開始下載:%s",realFileName));
info();
ExecutorService service = Executors.newFixedThreadPool(taskNum);
service.invokeAll(tasks);
service.shutdownNow();
timer.cancel();
File file = new File(tempFileName);
file.renameTo(new File(realFileName));
logger.info(String.format("完成下載:%s",realFileName));
}
/**
* 定時計算下載狀態
*/
private void info(){
timer.schedule(new TimerTask() {
@Override
public void run() {
long tempC = currentSize.get();
long tempSize = tempC-preSize;
preSize = tempC;
double percent = (tempC*100.0)/fileSize;
double speed = (tempSize*1.0)/1024/3;
logger.info(String.format("檔案:%s 已完成:%.2f%% 速率:%.2fkb/s",realFileName, percent, speed));
}
},0, 3000);
}
}
class BlockDownLoadTask implements Callable<String> {
private String tempFileName;
private String urlStr;
private long startPosition;
private long endPosition;
private AtomicLong currentSize;
BlockDownLoadTask(String tempFileName, String urlStr, long startPosition, long endPosition, AtomicLong currentSize) {
this.tempFileName = tempFileName;
this.urlStr = urlStr;
this.startPosition = startPosition;
this.endPosition = endPosition;
this.currentSize = currentSize;
}
@Override
public String call() throws Exception {
URL url = new URL(urlStr);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition);
InputStream is = connection.getInputStream();
RandomAccessFile randomAccessFile = new RandomAccessFile(tempFileName, "rwd");
randomAccessFile.seek(startPosition);
byte[] bs = new byte[1024];
int len;
while ((len = is.read(bs)) > 0) {
randomAccessFile.write(bs);
currentSize.addAndGet(len);
}
randomAccessFile.close();
is.close();
return Thread.currentThread().getName();
}
}