Java 斷點續(xù)傳
(1)原理
在下載行為出現(xiàn)中斷的時候,記錄下中斷的位置信息,然后在下次行為開始的時候,直接從記錄的這個位置開始下載內(nèi)容,而不再從頭開始。
分為兩步:
當(dāng)“上傳(下載)的行為”出現(xiàn)中斷,我們需要記錄本次上傳(下載)的位置(position)。
當(dāng)“續(xù)”這一行為開始,我們直接跳轉(zhuǎn)到postion處繼續(xù)上傳(下載)的行為。
(2)代碼
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
public class Test {
// step1:首先,我們定義了一個變量position,記錄在發(fā)生中斷的時候,已完成讀寫的位置。(這是為了方便,實際來說肯定應(yīng)該講這個值存到文件或者數(shù)據(jù)庫等進行持久化)
private static int position = -1;
public static void main(String[] args) {
// 源文件與目標(biāo)文件
File sourceFile = new File("D:/", "test.txt");
File targetFile = new File("E:/", "test.txt");
// 輸入輸出流
FileInputStream fis = null;
FileOutputStream fos = null;
// 數(shù)據(jù)緩沖區(qū)
byte[] buf = new byte[1];
try {
fis = new FileInputStream(sourceFile);
fos = new FileOutputStream(targetFile);
// 數(shù)據(jù)讀寫
while (fis.read(buf) != -1) {
fos.write(buf);
// step2:然后在文件讀寫的while循環(huán)中,我們?nèi)ツM一個中斷行為的發(fā)生。這里是當(dāng)targetFile的文件長度為3個字節(jié)則模擬拋出一個我們自定義的異常。(我們可以想象為實際下載中,已經(jīng)上傳(下載)了”x”個字節(jié)的內(nèi)容,這個時候網(wǎng)絡(luò)中斷了,那么我們就在網(wǎng)絡(luò)中斷拋出的異常中將”x”記錄下來)。
if (targetFile.length() == 3) {
position = 3;
throw new FileAccessException();
}
}
} catch (FileAccessException e) {
//step3:開啟”續(xù)傳“行為,即keepGoing方法.
keepGoing(sourceFile,targetFile, position);
} catch (FileNotFoundException e) {
System.out.println("指定文件不存在");
} catch (IOException e) {
// TODO: handle exception
} finally {
try {
// 關(guān)閉輸入輸出流
if (fis != null)
fis.close();
if (fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void keepGoing(File source,File target, int position) {
// step3.1:我們起頭讓線程休眠10秒鐘,這正是為了讓我們運行程序看到效果。
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// step3.2:在“續(xù)傳”行為開始后,通過RandomAccessFile類來包裝我們的文件,然后通過seek將指針指定到之前發(fā)生中斷的位置進行讀寫就搞定了。
(實際的文件下載上傳,我們當(dāng)然需要將保存的中斷值上傳給服務(wù)器,這個方式通常為
try {
RandomAccessFile readFile = new RandomAccessFile(source, "rw");
RandomAccessFile writeFile = new RandomAccessFile(target, "rw");
readFile.seek(position);
writeFile.seek(position);
// 數(shù)據(jù)緩沖區(qū)
byte[] buf = new byte[1];
// 數(shù)據(jù)讀寫
while (readFile.read(buf) != -1) {
writeFile.write(buf);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class FileAccessException extends Exception {
}
(3)實現(xiàn)結(jié)果
運行程序,那么文件就會開啟“由D盤上傳到E盤的過程”,我們首先點開E盤,會發(fā)現(xiàn)的確多了一個test.txt文件,打開它發(fā)現(xiàn)內(nèi)容如下:

在這里插入圖片描述
這個時候我們發(fā)現(xiàn)內(nèi)容只有“abc”。這是在我們預(yù)料以內(nèi)的,因為我們的程序模擬在文件上傳了3個字節(jié)的時候發(fā)生了中斷。
等待10秒鐘過去,然后再點開該文件,發(fā)現(xiàn)內(nèi)容的確已經(jīng)變成了“abc”,由此也就完成了續(xù)傳。

在這里插入圖片描述
android中的實現(xiàn)
import...;
2
3 public class MainActivity extends AppCompatActivity {
4
5 private EditText et_path;
6 private EditText et_threadCount;
7 private LinearLayout ll_pb;
8 private String path;
9
10 private static int runningThread;// 代表正在運行的線程
11 private int threadCount;
12 private List<ProgressBar> pbList;//集合存儲進度條的引用
13
14 @Override
15 protected void onCreate(Bundle savedInstanceState) {
16 super.onCreate(savedInstanceState);
17 setContentView(R.layout.activity_main);
18
19 et_path = findViewById(R.id.et_path);
20 et_threadCount = findViewById(R.id.et_threadCount);
21 ll_pb = findViewById(R.id.ll_pb);
22 //添加一個進度條的引用
23 pbList = new ArrayList<ProgressBar>();
24 }
25
26 //點擊按鈕實現(xiàn)下載邏輯
27 public void click(View view) {
28 //獲取下載路徑
29 path = et_path.getText().toString().trim();
30 //獲取線程數(shù)量
31 String threadCounts = et_threadCount.getText().toString().trim();
32 //移除以前的進度條添加新的進度條
33 ll_pb.removeAllViews();
34 threadCount = Integer.parseInt(threadCounts);
35 pbList.clear();
36 for (int i = 0; i < threadCount; i++) {
37 ProgressBar v = (ProgressBar) View.inflate(getApplicationContext(), R.layout.layout, null);
38
39 //把v添加到幾何中
40 pbList.add(v);
41
42 //動態(tài)獲取進度條
43 ll_pb.addView(v);
44 }
45
46 //java邏輯移植
47 new Thread() {
48 @Override
49 public void run() {
50 /*************/
51 System.out.println("你好");
52 try {
53 URL url = new URL(path);
54 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
55 conn.setRequestMethod("GET");
56 conn.setConnectTimeout(5000);
57 int code = conn.getResponseCode();
58 if (code == 200) {
59 int length = conn.getContentLength();
60 // 把運行線程的數(shù)量賦值給runningThread
61 runningThread = threadCount;
62
63 System.out.println("length=" + length);
64 // 創(chuàng)建一個和服務(wù)器的文件一樣大小的文件,提前申請空間
65 RandomAccessFile randomAccessFile = new RandomAccessFile(getFileName(path), "rw");
66 randomAccessFile.setLength(length);
67 // 算出每個線程下載的大小
68 int blockSize = length / threadCount;
69 // 計算每個線程下載的開始位置和結(jié)束位置
70 for (int i = 0; i < length; i++) {
71 int startIndex = i * blockSize;// 開始位置
72 int endIndex = (i + 1) * blockSize;// 結(jié)束位置
73 // 特殊情況就是最后一個線程
74 if (i == threadCount - 1) {
75 // 說明是最后一個線程
76 endIndex = length - 1;
77 }
78 // 開啟線程去服務(wù)器下載
79 DownLoadThread downLoadThread = new DownLoadThread(startIndex, endIndex, i);
80 downLoadThread.start();
81
82 }
83
84 }
85 } catch (MalformedURLException e) {
86 // TODO Auto-generated catch block
87 e.printStackTrace();
88 } catch (IOException e) {
89 // TODO Auto-generated catch block
90 e.printStackTrace();
91 }
92 /*************/
93 }
94 }.start();
95
96 }
97
98 private class DownLoadThread extends Thread {
99 // 通過構(gòu)造方法吧每個線程的開始位置和結(jié)束位置傳進來
100 private int startIndex;
101 private int endIndex;
102 private int threadID;
103 private int PbMaxSize;//代表當(dāng)前下載(進度條)的最大值
104 private int pblastPosition;//如果中斷過,這是進度條上次的位置
105
106 public DownLoadThread(int startIndex, int endIndex, int threadID) {
107 this.startIndex = startIndex;
108 this.endIndex = endIndex;
109 this.threadID = threadID;
110
111 }
112
113 @Override
114 public void run() {
115 // 實現(xiàn)去服務(wù)器下載文件
116 try {
117 //計算進度條最大值
118 PbMaxSize = endIndex - startIndex;
119 URL url = new URL(path);
120 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
121 conn.setRequestMethod("GET");
122 conn.setConnectTimeout(5000);
123 // 如果中間斷過,接著上次的位置繼續(xù)下載,聰慧文件中讀取上次下載的位置
124 File file = new File(getFileName(path) + threadID + ".txt");
125 if (file.exists() && file.length() > 0) {
126 FileInputStream fis = new FileInputStream(file);
127 BufferedReader bufr = new BufferedReader(new InputStreamReader(fis));
128 String lastPosition = bufr.readLine();
129 int lastPosition1 = Integer.parseInt(lastPosition);
130
131 //賦值給進度條位置
132 pblastPosition = lastPosition1 - startIndex;
133 // 改變一下startIndex的值
134 startIndex = lastPosition1 + 1;
135 System.out.println("線程id:" + threadID + "真實下載的位置:" + lastPosition + "-------" + endIndex);
136
137 bufr.close();
138 fis.close();
139
140 }
141
142 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
143 int code = conn.getResponseCode();
144 if (code == 206) {
145 // 隨機讀寫文件對象
146 RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
147 // 每個線程從自己的位置開始寫
148
149 raf.seek(startIndex);
150 InputStream in = conn.getInputStream();
151 // 把數(shù)據(jù)寫到文件中
152 int len = -1;
153 byte[] buffer = new byte[1024];
154 int totle = 0;// 代表當(dāng)前線程下載的大小
155 while ((len = in.read(buffer)) != -1) {
156 raf.write(buffer, 0, len);
157 totle += len;
158
159 // 實現(xiàn)斷點續(xù)傳就是把當(dāng)前線程下載的位置保存起來,下次再下載的時候按照上次下載的位置繼續(xù)下載
160 int currentThreadPosition = startIndex + totle;// 存到一個txt文本中
161 // 用來存儲當(dāng)前線程當(dāng)前下載的位置
162 RandomAccessFile raff = new RandomAccessFile(getFileName(path) + threadID + ".txt", "rwd");
163 raff.write(String.valueOf(currentThreadPosition).getBytes());
164 raff.close();
165
166 //設(shè)置進度條當(dāng)前的進度
167 pbList.get(threadID).setMax(PbMaxSize);
168 pbList.get(threadID).setProgress(pblastPosition + totle);
169 }
170 raf.close();
171 System.out.println("線程ID:" + threadID + "下載完成");
172 // 將產(chǎn)生的txt文件刪除,每個線程下載完成的具體時間不知道
173 synchronized (DownLoadThread.class) {
174 runningThread--;
175 if (runningThread == 0) {
176 //說明線程執(zhí)行完畢
177 for (int i = 0; i < threadCount; i++) {
178
179 File filedel = new File(getFileName(path) + i + ".txt");
180 filedel.delete();
181 }
182
183 }
184
185 }
186
187 }
188 } catch (MalformedURLException e) {
189 // TODO Auto-generated catch block
190 e.printStackTrace();
191 } catch (IOException e) {
192 // TODO Auto-generated catch block
193 e.printStackTrace();
194 }
195
196 }
197 }
198
199 public String getFileName(String path) {
200 int start = path.lastIndexOf("/") + 1;
201 String subString = path.substring(start);
202 String fileName = "/data/data/com.lgqrlchinese.heima76android_11_mutildownload/" + subString;
203 return fileName;
204
205 }
206 }