更新時(shí)間:2022-11-10 來(lái)源:黑馬程序員 瀏覽量:
1.斷點(diǎn)續(xù)傳的介紹
客戶端軟件斷點(diǎn)續(xù)傳指的是在下載或上傳時(shí),將下載或上傳任務(wù)(一個(gè)文件或一個(gè)壓縮包)人為的劃分為幾個(gè)部分,每一個(gè)部分采用一個(gè)線程進(jìn)行上傳或下載,如果碰到網(wǎng)絡(luò)故障,可以從已經(jīng)上傳或下載的部分開始繼續(xù)上傳下載未完成的部分,而沒有必要從頭開始上傳下載。從而達(dá)到讓用戶節(jié)省時(shí)間,提高速度的目的。
2.斷點(diǎn)續(xù)傳的環(huán)境要求
(1). 如果是基于http請(qǐng)求與響應(yīng)實(shí)現(xiàn)的斷點(diǎn)續(xù)傳,需要服務(wù)器支持"響應(yīng)一部分"數(shù)據(jù)的功能;(本案例采用的是tomcat7服務(wù)器,而tomcat7服務(wù)器是支持這個(gè)功能的)
(2). 在客戶端需要使用RandomAccessFile類對(duì)文件任意位置的數(shù)據(jù)進(jìn)行隨機(jī)讀寫操作;
3.java的RandomAccessFile類介紹
java的API中對(duì)RandomAccessFile類的解釋如下:
我對(duì)RandomAccessFile類的理解是:RandomAccessFile類是java提供的一個(gè)可以用于隨機(jī)讀寫文件內(nèi)容的類,我們可以對(duì)RandomAccessFile類關(guān)聯(lián)的文件中的任意位置和任意大小的數(shù)據(jù)進(jìn)行任意的讀寫操作;因此要想完成文件的斷點(diǎn)續(xù)傳操作,該類的使用是必不可少的!
4.斷點(diǎn)續(xù)傳的基本實(shí)現(xiàn)思路
5.斷點(diǎn)續(xù)傳的代碼實(shí)現(xiàn)
基礎(chǔ)環(huán)境搭建:
(1). 創(chuàng)建WEB的maven工程;
(2). 引入maven的tomcat7插件;
(3). 在webapp目錄下存放多個(gè)文件,以備測(cè)試斷點(diǎn)續(xù)傳下載使用;
java客戶端代碼實(shí)現(xiàn):
public class MyDownLoadClient {
public static String urlpath = "http://127.0.0.1:80/";
private static int threadCount = 5;
public static void main(String[] args) throws Exception {
// 讓用戶輸入要下載的文件名稱
Scanner sc = new Scanner(System.in);
System.out.println("請(qǐng)輸入要下載的文件名稱:");
String file = sc.next();
urlpath = urlpath.concat(file);
// 獲取文件總大小
URL url = new URL(urlpath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(2000);
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
int contentLength = conn.getContentLength();
System.out.println("length" + contentLength);
int part = contentLength / threadCount;
// 讀配置文件
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
CountDownLatch count;
InputStream in = MyDownLoadClient.class.getClassLoader().getResourceAsStream(file + ".properties");
if (in != null) {
// 說(shuō)明該文件不是第一次下載,需要斷點(diǎn)續(xù)傳
Properties p = new Properties();
p.load(in);
in.close();
Set<String> keys = p.stringPropertyNames();
count = new CountDownLatch(keys.size());
for (String key : keys) {
String value = p.getProperty(key);
String[] arr = value.split(",");
long start = Long.parseLong(arr[0]);
long end = Long.parseLong(arr[1]);
map.put(key,value);
new DownloadThread(start, end, key, map, count, file).start();
}
p.clear();
p = null;
} else {
count = new CountDownLatch(threadCount);
// 說(shuō)明該文件是第一次下載,直接下載即可
for (int i = 0; i < threadCount; i++) {
long startIndex = i * part; //每個(gè)線程起始下載位置
long endIndex = (i + 1) * part;//每個(gè)線程的結(jié)束位置
if (i == threadCount - 1) {//最后一個(gè)線程的結(jié)束位置
endIndex = contentLength;
}
map.put( String.valueOf(i),startIndex+","+endIndex);
new DownloadThread(startIndex, endIndex, String.valueOf(i), map, count, file).start();
}
}
// 等待任務(wù)完成,刪除配置文件
count.await();
new File(MyDownLoadClient.class.getClassLoader().getResource("").getPath(),file + ".properties").delete();
System.out.println("==========================下載任務(wù)完成==========================");
} else {
System.out.println("連接服務(wù)器失敗...請(qǐng)檢查服務(wù)器是否暢通及資源路徑是否正確...");
}
}
}
下載任務(wù)的線程代碼實(shí)現(xiàn):
class DownloadThread extends Thread {
private long startIndex;
private long endIndex;
private String threadId;
private ConcurrentHashMap<String, String> map;
private CountDownLatch count;
//private long subTotal = 0;
private String fileName;
public DownloadThread(long startIndex, long endIndex, String threadId, ConcurrentHashMap<String, String> map, CountDownLatch count, String fileName) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
this.map = map;
this.count = count;
this.fileName = fileName;
}
@Override
public void run() {
try {
URL url = new URL(MyDownLoadClient.urlpath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
//固定寫法,表示向服務(wù)器請(qǐng)求部分資源
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
int responseCode = conn.getResponseCode();
//狀態(tài)碼206表示請(qǐng)求部分資源成功
if (responseCode == 206) {
RandomAccessFile rafAccessFile = new RandomAccessFile(fileName, "rw");
rafAccessFile.seek(startIndex);
InputStream is = conn.getInputStream();
int len = -1;
byte[] buffer = new byte[1024];
Random r = new Random();
while ((len = is.read(buffer)) != -1) {
FileOutputStream fout = new FileOutputStream(this.getClass().getClassLoader().getResource("").getPath()+"\\"+fileName + ".properties");
try {
//模擬意外情況導(dǎo)致下載中斷的代碼
/*if (r.nextInt(2) == 0) {
int i = 1 / 0;
}*/
rafAccessFile.write(buffer, 0, len);
startIndex += len;
map.put(threadId, startIndex + "," + endIndex);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
} finally {
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
fout.write((entry.getKey() + "=" + entry.getValue() + "\r\n").getBytes());
}
fout.close();
}
}
rafAccessFile.close();
System.out.println("線程" + threadId + "下載完成");
System.gc();
}
count.countDown();
} catch (Exception e) {
e.printStackTrace();
System.gc();
}
}
}
6:功能測(cè)試
(1). 在web工程中提前準(zhǔn)備好要下載的文件;(任意類型,任意文件均可,本項(xiàng)目以三個(gè)api舉例)
(2). 啟動(dòng)tomcat服務(wù)器;(已經(jīng)設(shè)置虛擬目錄為 "/" 端口號(hào)為 "80")
(3). 啟動(dòng)java主程序類(MyDownLoadClient),輸入要下載的文件名;
(4). 可以通過打開線程任務(wù)中模擬意外情況的代碼,讓下載出現(xiàn)意外,當(dāng)程序出現(xiàn)意外后,配置文件不會(huì)刪除,且會(huì)記錄下所有線程已經(jīng)完成的下載量,以便于下次執(zhí)行下載任務(wù)的時(shí)候,可以在此基礎(chǔ)上繼續(xù)完成下載任務(wù);
(5). 關(guān)閉模擬意外的代碼,重新執(zhí)行程序,直到文件順利下載完成,程序會(huì)自動(dòng)刪除對(duì)應(yīng)的配置文件;
7.功能實(shí)現(xiàn)總結(jié)
斷點(diǎn)續(xù)傳最核心的思想就是利用RandomAccessFile類將一個(gè)大文件配合多線程拆分成多個(gè)片段進(jìn)行讀寫,最終將多個(gè)線程讀寫的結(jié)果再合并成1個(gè)大文件即可;
8.源代碼參考