1

私はダウンロード マネージャーを作成しています。ファイルの異なるセグメントをダウンロードする複数のスレッドが、一度に異なる場所でファイルに書き込むようにしたいと考えています。すべての明確化のために、ファイルをロックしたくありません。一度に書き込むさまざまなスレッドの目的を殺してしまうからです。Apache HttpClient ライブラリと FileChannel transferFrom() を使用しています。現在のコードは最初のセグメントのみをダウンロードし、他のセグメントを単に無視します。

コードの説明: startDownload メソッドは新しいファイルを作成し、リンクが部分的なコンテンツをサポートしているかどうかをチェックします。サポートしている場合は、各セグメントのスレッドを開始します。それ以外の場合は、単一のスレッドがファイル全体をダウンロードします。getFileName は、URI からファイル名を抽出する関数です。Download メソッドには、HttpClient を使用して実際にファイルをダウンロードし、transferFrom を使用してファイルを書き込むコードが含まれています。

    public void startDownload() {
    Thread thread = new Thread(() -> {
        try {
            String downloadDirectory = "/home/muhammad/";
            URI uri = new URI("http://94.23.204.158/JDownloader.zip");
            int segments = 2;
            // Create a HttpClient for checking file for segmentation.
            CloseableHttpClient Checkingclient = HttpClients.createDefault();
            // get request for checking size of file.
            HttpGet checkingGet = new HttpGet(uri);
            CloseableHttpResponse checkingResponse = Checkingclient.execute(checkingGet);
            long sizeofFile = checkingResponse.getEntity().getContentLength();
            // Create a new file in downloadDirectory with name extracted from uri.
            File file = new File(downloadDirectory + getFileName(uri));
            if (!file.exists()) {
                file.createNewFile();
            }
            // set range header for checking server support for partial content.
            checkingGet.setHeader("Range", "bytes=" + 0 + "-" + 1);
            checkingResponse = Checkingclient.execute(checkingGet);
            // Check if response code is 206 (partial content response code).
            if (checkingResponse.getStatusLine().getStatusCode() == 206) {
                //find size of each segment.
                final long sizeOfEachSegment = sizeofFile / segments;
                //Download each segment independently.
                for (int i = 0; i < segments; i++) {
                    Download(i * sizeOfEachSegment, (i + 1) * sizeOfEachSegment, sizeOfEachSegment, file, uri);
                }
                // Thread used for last few Bytes and EOF.
                Download(sizeOfEachSegment * segments, sizeofFile, Long.MAX_VALUE, file, uri);
            } else {
                System.err.println("server dont support partial content");
                System.out.println(checkingResponse.getStatusLine().getStatusCode());
                // Download complete file using single thread.
                Download(0, 0, Long.MAX_VALUE, file, uri);
            }
        } catch (IOException | URISyntaxException ex) {
            Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex);
        }
    });
    thread.start();
}
public void Download(long start, long end, long sizeOfEachSegment, File file, URI uri) {
    Thread thread = new Thread(() -> {
        try {
            FileChannel fileChannel = new FileOutputStream(file).getChannel();
            CloseableHttpClient client = HttpClients.createDefault();
            HttpGet get = new HttpGet(uri);
            // Range header for defining which segment of file we want to receive.
            if (end != 0) {
                String byteRange = start + "-" + end;
                get.setHeader("Range", "bytes=" + byteRange);
            }
            CloseableHttpResponse response = client.execute(get);
            ReadableByteChannel inputChannel = Channels.newChannel(response.getEntity().getContent());
            fileChannel.transferFrom(inputChannel, start, sizeOfEachSegment);
            response.close();
            client.close();
            fileChannel.close();
        } catch (IOException | IllegalStateException exception) {
            Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, exception);
        }
    });
    thread.start();
}

待機せずに複数のスレッドが同時に同じファイルに書き込むことができる既存のコードの修正は素晴らしいことですが、上記のタスクを実行できる場合は、他のより効率的な手法を研究することにも興味があります。いずれにせよ、待機せずにファイルに書き込むことが不可能な場合は、他の効率的なソリューションを歓迎します。前もって感謝します :)

4

2 に答える 2

2

異なるスレッドから同じファイルに書き込むことは、まったく役に立ちません。おそらく、スループットが劇的に低下することさえあります。

ファイルへの書き込みとキューからのフィードには、1 つのスレッドを使用する必要があります。

何かのようなもの:

class WriteBlock {
    long offset;
    byte[] data;
}
BlockingQueue<WriteBlock> writeQueue = new LinkedBlockingQueue<>();

これで、各ダウンロード スレッドはダウンロードからブロックを読み取り、 を作成WriteBlockしてキューに投稿する必要があります。

一方、書き込みスレッドはWriteBlocks をキューから吸い出し、できるだけ速く書き込みます。

キューにある間にブロックを並べ替える最適化があるかもしれませんが (おそらくPriorityBlockingQueueを使用)、最初に簡単な方法で行います。

于 2014-07-23T20:25:08.547 に答える