Javaのマルチスレッドとioストリームを利用して、サーバファイルを最も高速にダウンロード
7398 ワード
まず,マルチスレッドダウンロードの実現の大まかな考え方と,マルチスレッドダウンロードプロセスを用いる上で注意すべき問題について述べなければならない.
マルチスレッドダウンロードの実現の大まかな考え方:
つまり、1つのファイルリソース全体をいくつかの部分に分けて、いくつかのスレッドを開き、各スレッドが各サブ部分のファイルをダウンロードする責任を負うようにする.
スレッドダウンロードは非同期であり、ダウンロード時間を大幅に短縮し、最後にすべてのサブセクションを同じファイルに書き込み、最後に再編成して完全なファイルを得る.
まず、リソース全体のダウンロードについてお話しします.ネットワークリクエストを通じて、ファイル全体の入力ストリームを得ることができます.次に、ファイル全体ではなく、入力ストリームを得る必要があります.
各スレッドがダウンロードを担当するサブファイルの入力ストリームを取得します.そしてこれらの指定されたサイズの入力ストリームを得て、再び私たちのローカルファイルに書き込み、ストリームを書き込むときも注意する必要があります.
各サブセクションの入力フローは、対応するファイルの場所に書き込まなければなりません.そうしないと、前のセクションの書き込みファイルの入力フローが後の書き込みファイルの入力フローに上書きされます.
私たちが注意しなければならない問題をもう一度整理します.
質問1:ダウンロードリソース全体のサイズを取得する方法
問題2:ファイルリソース全体のサイズを取得した後、このファイルをどのように分割するか、各サブセクションファイルをどのように割り当て、異なるサブスレッドにダウンロードさせるか、つまりどのようにするか
各サブスレッドがファイルをダウンロードする区間(つまり、各サブスレッドがダウンロードを担当するサブセクションファイルのダウンロード開始とダウンロード終了の場所)を決定します.
問題3:サービス側がファイル全体の入力ストリームを取得するのではなく、指定したサイズの入力ストリームをどのように取得するか
問題4:各サブスレッドがファイルの適切な開始位置に書き込まれるようにするには、システムのデフォルトでは、ファイルにデータを書くたびに0位置から書き始めます.そうすると、後で
書き込まれたデータは、前に書き込まれたデータを上書きします.
3、上のいくつかの问题を一つ一つ打ち破って解决して、これらの问题がすべて解决したら、私达のマルチスレッドも実现しました
問題1の解決方法:
ダウンロードリソース全体のサイズを取得するのは簡単で、HttpURLConnectionネットワークリクエストによってHttpURLConnectionタイプの接続オブジェクトを直接得ることができます.
のgetContentLengthを使用して、リソースファイルをダウンロードする必要があるサイズを取得します.
もっと重要なのは、このファイルの大きさを手に入れて何をしますか??つまり、同じサイズの空きファイルをコピーしてローカルストレージスペースを占有することです.
どうしてこんなことをするの??実は細心の注意を払う人は発見して、私達が映画あるいはファイルをダウンロードする時私達はダウンロードのディレクトリの中で1つの臨時のファイルが現れることを発見します
この一時ファイルのサイズは、ダウンロードするファイルのサイズと一致し、このファイルは空白です.プロパティ・ファイルのサイズを右クリックして表示できます.
どうして空間を占有するのか、私たちはこの情景を想像することができて、もしコンピュータの中の貯蔵空間が1 Gしか残っていないならば、あなたがダウンロードした映画はちょうど1 Gで、映画はダウンロードしている過程です
もし事前にスペースを取らなかったら、ダウンロードの過程でまた1つの歌をダウンロードして、この時空間は明らかにこの映画を入れるのに足りないので、この映画はどうしますか?
そのため、このような事態を防止するために、いわゆる事前占有記憶領域が発生する.
問題2の解決方法:
ファイル全体をどのように分割するかは、各スレッドに各サブファイルのダウンロードタスクを担当させるため、スレッドの数によって直接分けましょうが、問題があります.
各スレッドに対して各サブセクションファイルの長さを平均的に割り当てることはできないので、n個のスレッドがあると仮定すると、前のn-1のサイズと同じように、最後の1つの方法を採用します.
残りのゼロヘッダ、すなわち最後のスレッドは、最後に残りのすべてのサブファイル長を処理します.
次のような式があります.
前のn-1スレッドのサブファイルサイズ:size=len/threadCount
これにより、各スレッドがサブファイルの長さを担当することが容易になります.
疑似コード:
int size=length/threadCount;
for (int id = 0; id < threadCount; id++) {
//1、各スレッドのダウンロード区間を決定する
//2、対応サブスレッドのダウンロードを開く
int startIndex=id*size;//各スレッドのサブセクションファイルの開始位置を決定
int endIndex=(id+1)*size-1;//各スレッドのサブセクションファイルの終了位置を決定
if(id=threadCount-1){//最後のスレッドであれば、そのサブファイルの終了位置を最後に伸ばすだけで、つまりファイル長-1
endIndex=length-1;
}
System.out.println(「第」+id+「個のスレッドのダウンロード区間は」+startIndex+「--」+endIndex);
問題3の解決方法:
サイズを決定する入力ストリームをどのように指定しますか?HttpURLConnectionオブジェクトにsetRequestPropertyメソッド設定ヘッダ情報が指定したサイズの入力ストリームを取得できます
conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
ただし、指定したサイズの入力ストリームを取得するには、要求されたサーバがマルチスレッドダウンロードをサポートする必要があります.
指定されたサイズの入力ストリームを取得する理由は、分割分子部分ファイルの長さに対応し、得られた指定されたサイズの入力ストリームを出力ストリームを介して対応するサイズのサブ部分ファイルに書き込むことである.
問題4の解決方法:
デフォルト設定(毎回0位置から書き始める)の影響を防止することで、後に書かれたデータが前に書かれたデータを上書きし、RandomAccessFileのseekメソッドによって各サブファイルからの
場所、つまりデフォルトを間接的に変更して毎回0から書き始め、各サブセクションファイルの開始位置から書きます.これにより、前のデータは上書きされません.
RandomAccessFile mAccessFile=new RandomAccessFile(file, "rwd");//「rwd」は読み取り可能、書き込み可能
mAccessFile.seek(startIndex);//別の場所からファイルを書くことを表す
上の4つの問題を解決することによって、全体の実現の構想を整理して、それでは私は実現過程を大体以下の5点にまとめます:
1、ダウンロードリソースファイルのサイズを取得し、同じサイズのランダムRandomAccessFile空白ファイルを生成し、スペースを占有する
2.RandomAccessFileの空白ファイルをいくつかの部分に分割し、各サブ部分ファイルのダウンロードスペースを確定する
3、対応するサブスレッドを開く
4.ネットワークサーバから指定されたサイズの部分入力ストリームを受け取る
5、RandomAccessFileファイルの異なる開始位置から、対応する指定サイズの入力ストリームを書き込む
マルチスレッドダウンロードの実現の大まかな考え方:
つまり、1つのファイルリソース全体をいくつかの部分に分けて、いくつかのスレッドを開き、各スレッドが各サブ部分のファイルをダウンロードする責任を負うようにする.
スレッドダウンロードは非同期であり、ダウンロード時間を大幅に短縮し、最後にすべてのサブセクションを同じファイルに書き込み、最後に再編成して完全なファイルを得る.
まず、リソース全体のダウンロードについてお話しします.ネットワークリクエストを通じて、ファイル全体の入力ストリームを得ることができます.次に、ファイル全体ではなく、入力ストリームを得る必要があります.
各スレッドがダウンロードを担当するサブファイルの入力ストリームを取得します.そしてこれらの指定されたサイズの入力ストリームを得て、再び私たちのローカルファイルに書き込み、ストリームを書き込むときも注意する必要があります.
各サブセクションの入力フローは、対応するファイルの場所に書き込まなければなりません.そうしないと、前のセクションの書き込みファイルの入力フローが後の書き込みファイルの入力フローに上書きされます.
私たちが注意しなければならない問題をもう一度整理します.
質問1:ダウンロードリソース全体のサイズを取得する方法
問題2:ファイルリソース全体のサイズを取得した後、このファイルをどのように分割するか、各サブセクションファイルをどのように割り当て、異なるサブスレッドにダウンロードさせるか、つまりどのようにするか
各サブスレッドがファイルをダウンロードする区間(つまり、各サブスレッドがダウンロードを担当するサブセクションファイルのダウンロード開始とダウンロード終了の場所)を決定します.
問題3:サービス側がファイル全体の入力ストリームを取得するのではなく、指定したサイズの入力ストリームをどのように取得するか
問題4:各サブスレッドがファイルの適切な開始位置に書き込まれるようにするには、システムのデフォルトでは、ファイルにデータを書くたびに0位置から書き始めます.そうすると、後で
書き込まれたデータは、前に書き込まれたデータを上書きします.
3、上のいくつかの问题を一つ一つ打ち破って解决して、これらの问题がすべて解决したら、私达のマルチスレッドも実现しました
問題1の解決方法:
ダウンロードリソース全体のサイズを取得するのは簡単で、HttpURLConnectionネットワークリクエストによってHttpURLConnectionタイプの接続オブジェクトを直接得ることができます.
のgetContentLengthを使用して、リソースファイルをダウンロードする必要があるサイズを取得します.
もっと重要なのは、このファイルの大きさを手に入れて何をしますか??つまり、同じサイズの空きファイルをコピーしてローカルストレージスペースを占有することです.
どうしてこんなことをするの??実は細心の注意を払う人は発見して、私達が映画あるいはファイルをダウンロードする時私達はダウンロードのディレクトリの中で1つの臨時のファイルが現れることを発見します
この一時ファイルのサイズは、ダウンロードするファイルのサイズと一致し、このファイルは空白です.プロパティ・ファイルのサイズを右クリックして表示できます.
どうして空間を占有するのか、私たちはこの情景を想像することができて、もしコンピュータの中の貯蔵空間が1 Gしか残っていないならば、あなたがダウンロードした映画はちょうど1 Gで、映画はダウンロードしている過程です
もし事前にスペースを取らなかったら、ダウンロードの過程でまた1つの歌をダウンロードして、この時空間は明らかにこの映画を入れるのに足りないので、この映画はどうしますか?
そのため、このような事態を防止するために、いわゆる事前占有記憶領域が発生する.
問題2の解決方法:
ファイル全体をどのように分割するかは、各スレッドに各サブファイルのダウンロードタスクを担当させるため、スレッドの数によって直接分けましょうが、問題があります.
各スレッドに対して各サブセクションファイルの長さを平均的に割り当てることはできないので、n個のスレッドがあると仮定すると、前のn-1のサイズと同じように、最後の1つの方法を採用します.
残りのゼロヘッダ、すなわち最後のスレッドは、最後に残りのすべてのサブファイル長を処理します.
次のような式があります.
前のn-1スレッドのサブファイルサイズ:size=len/threadCount
これにより、各スレッドがサブファイルの長さを担当することが容易になります.
疑似コード:
int size=length/threadCount;
for (int id = 0; id < threadCount; id++) {
//1、各スレッドのダウンロード区間を決定する
//2、対応サブスレッドのダウンロードを開く
int startIndex=id*size;//各スレッドのサブセクションファイルの開始位置を決定
int endIndex=(id+1)*size-1;//各スレッドのサブセクションファイルの終了位置を決定
if(id=threadCount-1){//最後のスレッドであれば、そのサブファイルの終了位置を最後に伸ばすだけで、つまりファイル長-1
endIndex=length-1;
}
System.out.println(「第」+id+「個のスレッドのダウンロード区間は」+startIndex+「--」+endIndex);
問題3の解決方法:
サイズを決定する入力ストリームをどのように指定しますか?HttpURLConnectionオブジェクトにsetRequestPropertyメソッド設定ヘッダ情報が指定したサイズの入力ストリームを取得できます
conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
ただし、指定したサイズの入力ストリームを取得するには、要求されたサーバがマルチスレッドダウンロードをサポートする必要があります.
指定されたサイズの入力ストリームを取得する理由は、分割分子部分ファイルの長さに対応し、得られた指定されたサイズの入力ストリームを出力ストリームを介して対応するサイズのサブ部分ファイルに書き込むことである.
問題4の解決方法:
デフォルト設定(毎回0位置から書き始める)の影響を防止することで、後に書かれたデータが前に書かれたデータを上書きし、RandomAccessFileのseekメソッドによって各サブファイルからの
場所、つまりデフォルトを間接的に変更して毎回0から書き始め、各サブセクションファイルの開始位置から書きます.これにより、前のデータは上書きされません.
RandomAccessFile mAccessFile=new RandomAccessFile(file, "rwd");//「rwd」は読み取り可能、書き込み可能
mAccessFile.seek(startIndex);//別の場所からファイルを書くことを表す
上の4つの問題を解決することによって、全体の実現の構想を整理して、それでは私は実現過程を大体以下の5点にまとめます:
1、ダウンロードリソースファイルのサイズを取得し、同じサイズのランダムRandomAccessFile空白ファイルを生成し、スペースを占有する
2.RandomAccessFileの空白ファイルをいくつかの部分に分割し、各サブ部分ファイルのダウンロードスペースを確定する
3、対応するサブスレッドを開く
4.ネットワークサーバから指定されたサイズの部分入力ストリームを受け取る
5、RandomAccessFileファイルの異なる開始位置から、対応する指定サイズの入力ストリームを書き込む
package com.mikyou.multithread;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* @author zhongqihong
*
* */
public class Main {
public static final String PATH="http://120.203.56.190:8088/upload/mobilelist.xml";
public static int threadCount=3;//
public static void main(String[] args) {
try {
URL url=new URL(PATH);
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
conn.connect();
if (conn.getResponseCode()==200) {
int length=conn.getContentLength();//
//
File file =new File("mobilelist.xml");
RandomAccessFile mAccessFile=new RandomAccessFile(file, "rwd");//"rwd" ,
mAccessFile.setLength(length);//
int size=length/threadCount;
for (int id = 0; id < threadCount; id++) {
//1、
//2、
int startIndex=id*size;
int endIndex=(id+1)*size-1;
if (id==threadCount-1) {
endIndex=length-1;
}
System.out.println(" "+id+" "+startIndex+"--"+endIndex);
new DownLoadThread(startIndex, endIndex, PATH, id).start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.mikyou.multithread;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
public class DownLoadThread extends Thread{
private int startIndex,endIndex,threadId;
private String urlString;
public DownLoadThread(int startIndex,int endIndex,String urlString,int threadId) {
this.endIndex=endIndex;
this.startIndex=startIndex;
this.urlString=urlString;
this.threadId=threadId;
}
@Override
public void run() {
try {
URL url=new URL(urlString);
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);// ,
if (conn.getResponseCode()==206) {// , , code 206
InputStream is=conn.getInputStream();
File file =new File("mobilelist.xml");
RandomAccessFile mAccessFile=new RandomAccessFile(file, "rwd");//"rwd" ,
mAccessFile.seek(startIndex);//
byte[] bs=new byte[1024];
int len=0;
int current=0;
while ((len=is.read(bs))!=-1) {
mAccessFile.write(bs,0,len);
current+=len;
System.out.println(" "+threadId+" "+current);
}
mAccessFile.close();
System.out.println(" "+threadId+" ");
}
} catch (Exception e) {
e.printStackTrace();
}
super.run();
}
}