通過HTTP協議實現多執行緒下載
1. 基本原理,每條執行緒從檔案不同的位置開始下載,最後合併出完整的資料。
2. 使用多執行緒下載的好處
下載速度快。為什麼呢?很好理解,以往我是一條執行緒在伺服器上下載。也就是說,對應在伺服器上,有一個我的下載執行緒存在。
這時候肯定不只我一個人在下載,伺服器上肯定同時存在多條下載執行緒,在下載伺服器資源。對於 CPU 來說,不可能實現併發執行。
CPU 會公平的為這些執行緒劃分時間片,輪流執行,a執行緒十毫秒 , b執行緒十毫秒...
假設運用了本文這種手法,意味著我的下載應用,可以同時使用伺服器端的任意多條執行緒同時下載(理論上).
假設這個執行緒數目是 50 條,本應用就將更多的得到伺服器 CPU 的照顧超過 50 倍.
但是總歸會受本地網路速度的限制。
3. 每條執行緒要負責下載的資料長度可以用 “下載資料的總長度” 除以 “參與下載的執行緒總數” 來計算。但是要考慮到不能整除的情況。
假設有 5 條執行緒參與下載,那麼計算公式應該為 :
int block = 資料總長度%執行緒數 == 0? 10/3 : 10/3+1; (不能整除,則加一)
4. 和資料庫分頁查詢型別。每條執行緒需要知道自己從資料的什麼位置開始下載,下載到什麼位置為止。
首先,為每一個執行緒配備一個 id , id 從零開始,為 0 1 2 3...
開始位置:執行緒 id 乘以每條執行緒負責下載的資料長度.
結束位置:下一個執行緒開始位置的前一個位置。
如:
int startPosition = 執行緒id * 每條執行緒下載的資料長度
int endPosition = (執行緒id + 1) * 每條執行緒下載的資料長度 -1;
5. HTTP 協議的 Range 頭可以指定從檔案的什麼位置開始下載,下載到什麼位置結束。單位為 1byte
Range:bytes=2097152-4194304 表示從檔案的 2M 的位置開始下載,下載到 4M 處結束
假如 Range 指定要讀取到 檔案的 5104389 的位元組數位置,但是下載的檔案本身只有 4104389 個長度。那麼下載操作自動會在 4104389 處停止。
因此不會下載到多餘的無效資料.
6. 另一個難題是如何按順序將資料寫往本地檔案。因為,執行緒是同步執行的,它們同時在往本地目標檔案寫入資料。
而執行緒於執行緒之間寫入的資料並沒有按照下載資料本身的順序。若按照普通的 OutputStream 的寫入方式,最後的本地下載檔案將失真。
於是我們將用到下面這個類:
java.io.RandomAccessFile
因為此類同時實現了 DataOutput 和 DataInput 的方法。使他們同時具有寫入和讀取功能。
這個類彷彿存在一個類似檔案指標的東西,可以隨意執行檔案的任意一個位置開始讀寫.
因此此類的例項支援對隨機訪問檔案的讀取和寫入.
例如:
Java程式碼
- File file = new File("1.txt");
- RandomAccessFile accessFile = new RandomAccessFile(file,"rwd");
- accessFile.setLength(1024);
雖然,執行完這段程式碼後,我們還沒有向目標檔案 "1.txt" 寫入任何資料。但是如果此時檢視其大小,已經為 1kb 了。這是我們自己設定的大小。
這個操作類似於向這個檔案儲存了一個大型的 byte 陣列。這個陣列將這個檔案撐到指定大小。等待被填滿。
既然是這樣,好處就在於,我們可以通過 “索引” 隨機訪問此檔案系統的某個部分。
例如,可能這個檔案大小為 500
那麼,我的業務需求可能需要第一次 從 300 位置開始寫資料,寫到 350 為止。
第二次,我又從 50 開始寫資料,寫到 100 為止。
總之,我不是 “一次性” 的 “按順序” 的將這個檔案寫完。
那麼,RandomAccessFile 可以支援這種操作。
API
void setLength(long newLength)
Sets the length of this file. (設定檔案的預計大小)
void seek(long pos)
Sets the file-pointer offset, measured from the beginning of this file, at which the next read or write occurs.
假設為這個方法傳入 1028 這個引數,表示,將從檔案的 1028 位置開始寫入。
void write(byte[] b, int off, int len)
Writes len bytes from the specified byte array starting at offset off to this file.
write(byte[] b)
Writes b.length bytes from the specified byte array to this file, starting at the current file pointer.
void writeUTF(String str)
Writes a string to the file using modified UTF-8 encoding in a machine-independent manner.
String readLine()
Reads the next line of text from this file.
實驗程式碼:
Java程式碼
- publicstaticvoid main(String[] args) throws Exception {
- File file = new File("1.txt");
- RandomAccessFile accessFile = new RandomAccessFile(file,"rwd");
- /* 設定檔案為 3 個位元組大小 */
- accessFile.setLength(3);
- /* 向第二個位置寫入 '2' */
- accessFile.seek(1);
-
accessFile.write("2
- /* 向第一個位置寫入 '1' */
- accessFile.seek(0); accessFile.write("1".getBytes());
- /* 向第三個位置寫入 '3' */
- accessFile.seek(2);
- accessFile.write("3".getBytes()); accessFile.close();
- // 期待檔案的內容為 :123
- }
以上實驗成功,雖然我們寫入字串的順序為 "2"、"1"、"3",但是因為設定了檔案偏移量的關係,檔案最終儲存的資料為 : 123
另一個疑問,寫完這三個資料,檔案的大小已經為 3 個位元組大小了。已經撐滿了寫入的資料,那麼我們繼續往裡面放資料會有什麼效果?
/* 向超出大小的第四個位元組位置寫入資料 */
accessFile.seek(3);
accessFile.write("400".getBytes());
以上程式碼無論 seek 方法指定的檔案指標偏移量以及存入的資料,都已經超出了最開始為檔案設定的 3 個位元組的大小。
按照我的猜測,至少 “accessFile.seek(3)” 位置會丟擲 "ArrayIndexOutOfBoundsException" 異常,表示下標越界。
而,單獨執行 "accessFile.write("400".getBytes())" 應該可以成功。因為這個需求屬於合理的,應該有執行它的機制。
實驗結果是兩句程式碼都是成功的。貌似是說明,檔案隱含的大型的位元組陣列,可以自動撐大。
但是要注意的問題是,必須要保證所設定的檔案大小的每一個位置都具有合法的資料,至少不能為空。
例如:
/* 向第三個位置寫入 '3' */
accessFile.seek(2);
accessFile.write("3".getBytes());
accessFile.seek(5);
accessFile.write("400".getBytes());
那麼結合之前的程式碼,最後的結果為:
123口口400
在空白的兩個位置處出現了亂碼。這是理所應當的。
另外,假設我們為檔案指定了一百個長度:
accessFile.setLength(100);
而,實際上,我們只為其前五個位置設定了值。那麼理所當然的是,檔案儲存的資料,最後會綴上 95 個亂碼。
7. 準備工作應該十分充分了。接下來上程式碼。
Java程式碼
- import java.io.File;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.RandomAccessFile;
- import java.net.HttpURLConnection;
- import java.net.URL;
- /**
- * 多執行緒方式檔案下載
- */
- publicclass MulThreadDownload {
- /* 下載的URL */
- private URL downloadUrl;
- /* 用於儲存的本地檔案 */
- private File localFile;
- /* 沒條執行緒下載的資料長度 */
- privateint block;
- publicstaticvoid main(String[] args) {
- /* 可以為網路上任意合法下載地址 */
- String downPath = "http://192.168.1.102:8080/myvideoweb/down.avi";
- MulThreadDownload threadDownload = new MulThreadDownload();
- /* 開 10 條執行緒下載下載 */
- try {
- threadDownload.download(downPath, 10);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 多執行緒檔案下載
- *
- * @param path 下載地址
- * @param threadCount 執行緒數
- */
- publicvoid download(String path, int threadCount) throws Exception {
- downloadUrl = new URL(path);
- HttpURLConnection conn = (HttpURLConnection) downloadUrl
- .openConnection();
- /* 設定 GET 請求方式 */
- conn.setRequestMethod("GET");
- /* 設定響應時間超時為 5 秒 */
- conn.setConnectTimeout(5 * 1000);
- /* 獲取本地檔名 */
- String filename = parseFilename(path);
- /* 獲取下載檔案的總大小 */
- int dataLen = conn.getContentLength();
- if (dataLen < 0) {
- System.out.println("獲取資料失敗");
- return;
- }
- /* 建立本地目標檔案,並設定其大小為準備下載檔案的總大小 */
- localFile = new File(filename);
- RandomAccessFile accessFile = new RandomAccessFile(localFile, "rwd");
- /* 這時候,其實本地目錄下,已經建立好了一個大小為下載檔案的總大小的檔案 */
- accessFile.setLength(dataLen);
- accessFile.close();
- /* 計算每條執行緒要下載的資料大小 */
- block = dataLen % threadCount == 0 ? dataLen / threadCount : dataLen / threadCount + 1;
- /* 啟動執行緒下載檔案 */
- for (int i = 0; i < threadCount; i++) {
- new DownloadThread(i).start();
- }
- }
- /**
- * 解析檔案
- */
- private String parseFilename(String path) {
- return path.substring(path.lastIndexOf("/") + 1);
- }
- /**
- * 內部類: 檔案下載執行緒類
- */
- privatefinalclass DownloadThread extends Thread {
- /* 執行緒 id */
- privateint threadid;
- /* 開始下載的位置 */
- privateint startPosition;
- /* 結束下載的位置 */
- privateint endPosition;
- /**
- * 新建一個下載執行緒
- * @param threadid 執行緒 id
- */
- public DownloadThread(int threadid) {
- this.threadid = threadid;
- startPosition = threadid * block;
- endPosition = (threadid + 1) * block - 1;
- }
- @Override
- publicvoid run() {
- System.out.println("執行緒 '" + threadid + "'啟動下載..");
- RandomAccessFile accessFile = null;
- try {
- /* 設定從本地檔案的什麼位置開始寫入資料 ,"rwd" 表示對檔案具有讀寫刪許可權 */
- accessFile = new RandomAccessFile(localFile, "rwd");
- accessFile.seek(startPosition);
- HttpURLConnection conn = (HttpURLConnection) downloadUrl.openConnection();
- conn.setRequestMethod("GET");
- conn.setReadTimeout(5 * 1000);
- /* 為 HTTP 設定 Range 屬性,可以指定伺服器返回資料的範圍 */
- conn.setRequestProperty("Range", "bytes=" + startPosition + "-"
- + endPosition);
- /* 將資料寫往本地檔案 */
- writeTo(accessFile, conn);
- System.out.println("執行緒 '" + threadid + "'完成下載");
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- if(accessFile != null) {
- accessFile.close();
- }
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- }
- /**
- * 將下載資料寫往本地檔案
- */
- privatevoid writeTo(RandomAccessFile accessFile,
- HttpURLConnection conn){
- InputStream is = null;
- try {
- is = conn.getInputStream();
- byte[] buffer = newbyte[1024];
- int len = -1;
- while ((len = is.read(buffer)) != -1) {
- accessFile.write(buffer, 0, len);
-
相關推薦
通過HTTP協議實現多執行緒下載
1. 基本原理,每條執行緒從檔案不同的位置開始下載,最後合併出完整的資料。 2. 使用多執行緒下載的好處 下載速度快。為什麼呢?很好理解,以往我是一條執行緒在伺服器上下載。也就是說,對應在伺服器上,有一個我的下載執行緒存在。 這時候肯定不只我一個人
libcurl實現多執行緒下載器
libcurl官網(http://curl.haxx.se/)是一個很強大網路功能的庫,支援當前DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS,
java使用 網路連線+RandomAccessFile + io 多執行緒實現多執行緒下載檔案並顯示實時網速
下載檔案的時候,一個大檔案切成很多片,用多執行緒下載,速度會快很多 閱讀程式碼的時候注意檢視程式碼裡面的註釋 想用多執行緒下載檔案,則, 第一:得了解 RandomAccessFile 類,這是個隨機訪問檔案類,裡面可以設定 訪問的 開始地址和結束地址,且該類可讀可
Android實現多執行緒下載檔案,支援斷點
本篇部落格主要介紹多執行緒去下載檔案,以下載多個app為例。不管去下在app,音視訊等檔案,實現起來都一樣。篇幅有點長,先貼張美女圖看看 正在下載的效果圖 下載完成效果圖 小編的下載路徑是放在sd卡的絕對路徑中,方便驗證! 工程目錄圖 介紹下每
Android 實現多執行緒下載檔案+斷點續傳
Android 多執行緒下載檔案+斷點續傳 在專案快要結束的時候,發現了app沒有版本更新的功能,於是找到一些過去的資料,在app上應用完成了版本更新,現在記錄一下apk的下載,也就是如何通過多執行緒將ap
Android實現多執行緒下載並顯示通知
1、前言 相信大家都使用過一些可以下載檔案的 App,在下載列表中通常都會顯示一個進度條實時地更新下載進度,現在的下載都是斷點重傳的,也就是在暫停後,重新下載會依照之前進度接著下載。 我們這個下載的案例是有多個執行緒同時下載一個任務,並能提供多個檔案同時下載
http檔案批量多執行緒下載之winform
這裡簡單的介紹一種從http獲取檔案然後下載到本地的方法,開始我用單執行緒下載,檔案多的情況下速度太慢了,後來就採用多執行緒,這裡琢磨了好久才整出來一個。這裡的部分程式碼是在部落格園找到的,具體是在哪裡不是很清楚了,搜尋關鍵詞大概是http下載檔案,感謝下。 1.關於多執行
使用Apache HttpClient實現多執行緒下載的小例子
網上類似的文章很多,參考了很多人的,大部分人都是用URLConnection寫的。 原理一:HTTP多執行緒下載原理 1、傳送一個含有Rang頭的Head請求,如果返回狀態碼為206,則允許多執行緒下載 原理二:多執行緒下載原理 1、使用HttpClient的Head請求獲取請求檔案的資訊 2、傳送一個
通過閉鎖方式實現多執行緒同時併發測試
閉鎖是一種同步工具類,可以延遲執行緒的進度直到其達到終止狀態。形象一點就是,閉鎖就是一扇關閉的大門,在閉鎖達到結束條件之前,這扇門是關閉的,沒有任何執行緒可以通過。而一旦條件達到,就像開閘洩洪一樣,萬馬奔騰,瞬間達到高併發。在此我希望模擬高併發的瞬間,而不是依次的啟動執行緒
Java通過DelayQueue的實現多執行緒任務的阻塞佇列
2)例項程式碼,實現任務元素的延遲佇列://定義延遲佇列中的任務元素,可以通過extends實現不同的run方法; class DelayedTask implements Runnable,Delayed{ private final int delta; //差值訊號:用於記錄任務執行緒的延
使用xutils實現多執行緒下載
這個開源專案在github可以下載到。 HttpUtils http = new HttpUtils(); /** * 2 進行下載 * url 下載的路徑 * target 存放目標地址 *
實現多執行緒下載同一個檔案
原理: 示例程式碼: public class LoadFile { private static int threadCount = 3;// 下載的執行緒數量 public static
java多執行緒下載http協議檔案
package com.wild.http; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream
Http的多執行緒下載的實現
a、對於網路上的一個資源,首先發送一個請求,從返回的Content-Length中回去需要下載檔案的大小,然後根據檔案大小建立一個檔案。 b、根據執行緒數和檔案大小,為每個執行緒分配下載的位元組區間,然後每個執行緒向伺服器傳送請求,獲取這段位元組區間的檔案內容
Java多執行緒下載原理與實現
多執行緒下載原理 客戶端要下載一個檔案, 首先請求伺服器,伺服器將這個檔案傳送給客戶端,客戶端儲存到本地, 完成了一個下載的過程. 多執行緒下載的思想是客戶端開啟多個執行緒同時下載,每個執行緒只負責下載檔案的一部分, 當所有執行緒下載完成的時候,檔案下載完畢.
通過實現runnable實現多執行緒操作
第一步:建立一個抽象類,實現runnable介面。 public abstract class ThreadRun implements Runnable { @Override public void run() { doSomeThing();
OkHttp實現多執行緒併發下載的筆記
打個廣告,不瞭解OkHttp的話可以先看下 http://blog.csdn.net/brycegao321/article/details/51830525 需求: 手機拍攝若干張照片, 在wifi連線下上傳到伺服器。
基於TCP協議實現Linux下客戶端與伺服器之間的通訊,實現多執行緒、多程序伺服器
TCP是TCP/IP協議族中一個比較重要的協議,這是一種可靠、建立連結、面向位元組流的傳輸,工作在傳輸層。和TCP相對的不可靠、無連結、面向資料報的協議UDP,瞭解UDP客戶端與伺服器之間通訊請戳UDP協議實現的伺服器與客戶端通訊 TCP協議建立連線 首
Java多執行緒下載技術實現
多執行緒下載 多執行緒下載技術,簡單的說就是把要下載的檔案分成幾塊,由不同的執行緒來負責每一塊資料的下載任務。 技術要點 RandomAccessFile: Java中用來實現隨機訪問檔案的類 http Range請求頭 具體思路 1、檔案分塊。 檔案分塊大小(blockSize)= (
linux 下基於特定通訊協議利用多執行緒同步通訊機制實現的串列埠通訊
</pre><pre name="code" class="cpp">/** *@Title:利用多執行緒同步通訊機制實現串列埠通訊 *@Introduce:主要完成根據特定的通訊協議實現串列埠與PC上特定串列埠 * 通訊軟體的通訊。測試版,只