多線程下載可以搶佔其它相同優先級用戶的網絡資源(寬帶),所以說下載速度比較快,迅雷、快播都使用了多線程下載。
1.請求服務器上的文件的長度
2.根據服務器上的文件長度在手機上創建一個一模一樣大小的文件
3.根據線程的個數和文件的長度來計算每一個線程需要下載的範圍
文件的長度爲:10 線程的數量爲:3 每一塊的大小:10/3=3.3333=3
線程0:0~2
線程1:3~5
線程2:6~9
開始的位置:線程的id*每一塊的大小。
結束的位置:線程的(id+1 ) *每一塊的大小 -1;
最後一個線程結束的位置是:文件的長度-1;
4.開啓多個線程,每一個線程下載自己的那塊範圍
1).請求服務器上傳的部分內容。
Range(請求頭): bytes=3-8獲取服務器文件3-8的內容
特點:服務器返回值不再是200,是206.
2).把寫入的位置移動到線程開始的位置上。
3).循環的讀取內容寫入到本地。
####斷點下載(非正常關閉)
*在每一次讀取到數據,寫入本地數據時,應該把總記錄存到本地。
*當下一次開始下載時,先查看有沒有下載的記錄(存檔),如果有就取出來,繼續下載
代碼:
public class DownloadUtils {
// 完成的線程
private int completeThread;
private int threadCount;
/**
* 開始多線程下載
*
* @param url
* 訪問的服務器地址
* @param dir
* 本地接收路徑
* @param threadCount
* 開啓多少條線程下載
*/
public void startMultiThreadDownload(final String url, final String dir,
final int threadCount) {
this.threadCount = threadCount;
completeThread = 0;
new Thread(new Runnable() {
@Override
public void run() {
// 1.請求服務器上的文件的長度
int fileLength = getRemoteServiceFileLength(url);
if (fileLength == -1) {
System.out.println("請求服務器文件長度失敗,停止下載");
return;
}
System.out.println("遠程服務器的長度爲:" + fileLength);
// 2.根據服務器上的文件長度在手機上創建一個一模一樣大小的文件
String fileName = url.substring(url.lastIndexOf("/") + 1);
File file = new File(dir, fileName);
try {
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.setLength(fileLength);
raf.close();
System.out.println("本地文件創建成功" + file.getPath());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 3.根據線程的個數和文件的長度來計算每一個線程需要下載的範圍
int blockSize = fileLength / threadCount;
for (int i = 0; i < threadCount; i++) {
// 開始的位置:線程的id*每一塊的大小。
int start = i * blockSize;
// 結束的位置:線程的(id+1 ) *每一塊的大小 -1;
int end = (i + 1) * blockSize - 1;
// 最後一個線程結束的位置是:文件的長度-1;
if ((i == threadCount - 1)) {
end = fileLength - 1;
}
// 4.開啓多個線程,每一個線程下載自己的那塊範圍,傳遞參數:下載地址,存儲文件的路徑
// 開始的位置,結束的位置,線程ID
new Thread(new DownloadRunnable(url, file.getPath(), start,
end, i)).start();
}
}
}).start();
}
/**
* 下載任務類
*
* @author Administrator
*
*/
class DownloadRunnable implements Runnable {
private String url;
private File localFile; // 文件的路徑
private int start; // 當前線程開始下載的位置
private int end; // 結束下載的位置
private int threadID;// 線程ID
public DownloadRunnable(String url, String path, int start, int end,
int i) {
this.url = url;
this.localFile = new File(path);
this.start = start;
this.end = end;
this.threadID = i;
}
@Override
public void run() {
HttpURLConnection conn = null;
try {
// 添加斷點下載
int total = 0;// 當前線程下載的總進度
// 把總進度值存儲到本地去
File cacheFile = new File(localFile.getParent(), "thread"
+ threadID + ".hm");
RandomAccessFile cacheRAF = new RandomAccessFile(cacheFile,
"rwd");
// 判斷上一次是否有緩存的進度
if (cacheFile.exists() && cacheFile.length() > 0) {// 文件存在並且有數據
// 上一次下載的進度
int progress = Integer.valueOf(cacheRAF.readLine());
start += progress;// 把上一次的進度在開始的位置加上,繼續下載
total = progress;// 把上一次下載總進度賦值
}
System.out.println("線程" + threadID + ",開始下載了,下載的範圍是:" + start
+ " ~" + end);
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
// 設置請求頭消息:Range,請求服務器上文件的部分內容
conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
int responseCode = conn.getResponseCode();
if (responseCode == 206) {
int contentLength = conn.getContentLength();
System.out.println("線程" + threadID + ",請求成功,內容長度爲:"
+ contentLength);
// 得到服務器返回的數據
InputStream is = conn.getInputStream();
RandomAccessFile raf = new RandomAccessFile(localFile,
"rwd");
// 把寫入的位置移動到線程開始的位置上
raf.seek(start);
byte[] buf = new byte[1024];
int len = 0;
while ((len = is.read(buf)) != -1) {
raf.write(buf, 0, len);
total += len;
cacheRAF.seek(0);// 每一次都移動到文件的開始位置進行寫入
cacheRAF.write(String.valueOf(total).getBytes());
}
cacheRAF.close();
raf.close();
is.close();
System.out.println("線程" + threadID + ",下載完成了.");
completeThread++;
if (completeThread == threadCount) {
System.out.println("全部下載完整,刪除臨時配置文件.");
for (int i = 0; i < threadCount; i++) {
File deleteFile = new File(localFile.getParent(),
"thread" + i + ".hm");
System.out.println(deleteFile.delete());
}
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
}
/**
* 根據URL連接請求遠程服務器獲取文件大小
*
* @param url
* 服務器地址
* @return
*/
private int getRemoteServiceFileLength(String url) {
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
// 請求方式,必須大小
conn.setRequestMethod("GET");
// 連接超時
conn.setConnectTimeout(5000);
// 讀取超時
conn.setReadTimeout(5000);
int reponseCode = conn.getResponseCode();// 獲取的服務器返回的響應碼
if (reponseCode == 200) {
return conn.getContentLength();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
return -1;
}
}
調用:
public class Demo {
public static void main(String[] args) {
System.out.println("開啓多線程下載了............");
// 鏈接,路徑,線程的數量
String url = "http://192.168.1.109/FeiQ.exe";
String dir = "F:/WDJDownload";
int threadCount = 3;
DownloadUtils du = new DownloadUtils();
du.startMultiThreadDownload(url, dir, threadCount);
}
}