更新時間:2022-11-10 來源:黑馬程序員 瀏覽量:

1.斷點續(xù)傳的介紹
客戶端軟件斷點續(xù)傳指的是在下載或上傳時,將下載或上傳任務(一個文件或一個壓縮包)人為的劃分為幾個部分,每一個部分采用一個線程進行上傳或下載,如果碰到網(wǎng)絡故障,可以從已經(jīng)上傳或下載的部分開始繼續(xù)上傳下載未完成的部分,而沒有必要從頭開始上傳下載。從而達到讓用戶節(jié)省時間,提高速度的目的。
2.斷點續(xù)傳的環(huán)境要求
(1). 如果是基于http請求與響應實現(xiàn)的斷點續(xù)傳,需要服務器支持"響應一部分"數(shù)據(jù)的功能;(本案例采用的是tomcat7服務器,而tomcat7服務器是支持這個功能的)
(2). 在客戶端需要使用RandomAccessFile類對文件任意位置的數(shù)據(jù)進行隨機讀寫操作;
3.java的RandomAccessFile類介紹
java的API中對RandomAccessFile類的解釋如下:

我對RandomAccessFile類的理解是:RandomAccessFile類是java提供的一個可以用于隨機讀寫文件內(nèi)容的類,我們可以對RandomAccessFile類關聯(lián)的文件中的任意位置和任意大小的數(shù)據(jù)進行任意的讀寫操作;因此要想完成文件的斷點續(xù)傳操作,該類的使用是必不可少的!
4.斷點續(xù)傳的基本實現(xiàn)思路

5.斷點續(xù)傳的代碼實現(xiàn)
基礎環(huán)境搭建:
(1). 創(chuàng)建WEB的maven工程;
(2). 引入maven的tomcat7插件;
(3). 在webapp目錄下存放多個文件,以備測試斷點續(xù)傳下載使用;
java客戶端代碼實現(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("請輸入要下載的文件名稱:");
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) {
// 說明該文件不是第一次下載,需要斷點續(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);
// 說明該文件是第一次下載,直接下載即可
for (int i = 0; i < threadCount; i++) {
long startIndex = i * part; //每個線程起始下載位置
long endIndex = (i + 1) * part;//每個線程的結束位置
if (i == threadCount - 1) {//最后一個線程的結束位置
endIndex = contentLength;
}
map.put( String.valueOf(i),startIndex+","+endIndex);
new DownloadThread(startIndex, endIndex, String.valueOf(i), map, count, file).start();
}
}
// 等待任務完成,刪除配置文件
count.await();
new File(MyDownLoadClient.class.getClassLoader().getResource("").getPath(),file + ".properties").delete();
System.out.println("==========================下載任務完成==========================");
} else {
System.out.println("連接服務器失敗...請檢查服務器是否暢通及資源路徑是否正確...");
}
}
}
下載任務的線程代碼實現(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);
//固定寫法,表示向服務器請求部分資源
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
int responseCode = conn.getResponseCode();
//狀態(tài)碼206表示請求部分資源成功
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 {
//模擬意外情況導致下載中斷的代碼
/*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:功能測試
(1). 在web工程中提前準備好要下載的文件;(任意類型,任意文件均可,本項目以三個api舉例)

(2). 啟動tomcat服務器;(已經(jīng)設置虛擬目錄為 "/" 端口號為 "80")

(3). 啟動java主程序類(MyDownLoadClient),輸入要下載的文件名;

(4). 可以通過打開線程任務中模擬意外情況的代碼,讓下載出現(xiàn)意外,當程序出現(xiàn)意外后,配置文件不會刪除,且會記錄下所有線程已經(jīng)完成的下載量,以便于下次執(zhí)行下載任務的時候,可以在此基礎上繼續(xù)完成下載任務;



(5). 關閉模擬意外的代碼,重新執(zhí)行程序,直到文件順利下載完成,程序會自動刪除對應的配置文件;

7.功能實現(xiàn)總結
斷點續(xù)傳最核心的思想就是利用RandomAccessFile類將一個大文件配合多線程拆分成多個片段進行讀寫,最終將多個線程讀寫的結果再合并成1個大文件即可;
8.源代碼參考
1024首播|39歲程序員逆襲記:不被年齡定義,AI浪潮里再迎春天
2025-10-241024程序員節(jié)丨10年同行,致敬用代碼改變世界的你
2025-10-24【AI設計】北京143期畢業(yè)僅36天,全員拿下高薪offer!黑馬AI設計連續(xù)6期100%高薪就業(yè)
2025-09-19【跨境電商運營】深圳跨境電商運營畢業(yè)22個工作日,就業(yè)率91%+,最高薪資達13500元
2025-09-19【AI運維】鄭州運維1期就業(yè)班,畢業(yè)14個工作日,班級93%同學已拿到Offer, 一線均薪資 1W+
2025-09-19【AI鴻蒙開發(fā)】上海校區(qū)AI鴻蒙開發(fā)4期5期,距離畢業(yè)21天,就業(yè)率91%,平均薪資14046元
2025-09-19