Javaで一定の速度でファイルを読み取る
-
22-08-2019 - |
質問
長いファイルを一定の速度で読み取る方法に関する記事/アルゴリズムはありますか?
読み取りの発行中に 10 KB/秒を渡したくないとします。
解決
大まかな解決策は、一度にチャンクを読み取ってから、たとえば 10k スリープしてから 1 秒スリープするだけです。しかし、最初に尋ねなければならない質問は次のとおりです。なぜ?考えられる答えはいくつかあります。
- 作業を完了できるよりも早く作成することは望ましくありません。または
- システムに過大な負荷を与えたくないでしょう。
私の提案は、読み取りレベルで制御しないことです。それはちょっと乱雑で不正確です。代わりに、作業側で制御してください。Java には、これに対処するための優れた同時実行ツールが数多くあります。これを行う別の方法がいくつかあります。
私は、を使用するのが好きな傾向があります 生産者 消費者 この種の問題を解決するためのパターン。レポートスレッドなどを用意して進行状況を監視できる優れたオプションを提供しており、非常にクリーンなソリューションとなります。
のようなもの 配列ブロックキュー (1) と (2) の両方に必要な種類のスロットリングに使用できます。容量が限られているため、キューがいっぱいになるとリーダーは最終的にブロックされるため、すぐにいっぱいになることはありません。労働者 (消費者) は、レート カバレッジを抑制するために非常に速く働くように制御することもできます (2)。
他のヒント
ThrottledInputStreamを作成することにより、簡単な解決策、。
これは、このように使用する必要があります:
final InputStream slowIS = new ThrottledInputStream(new BufferedInputStream(new FileInputStream("c:\\file.txt"),8000),300);
300秒あたりのキロバイト数です。 8000はBufferedInputStreamをするためのブロックサイズです。
このはもちろん、あなたのSystem.currentTimeMillis()の呼び出しのトンを惜しまれる、(バイトb []、オフのint型、int型のlen)の読み取りを実現することによって一般化されなければなりません。 System.currentTimeMillis()はオーバーヘッドのビットを引き起こす可能性が、各バイトの読み出しのために一度呼び出されます。また、savelyのSystem.currentTimeMillis()を呼び出すことなく読み込むことができるバイト数を格納することが可能でなければならない。
それ以外のFileInputStreamは、シングルバイトではなくブロックでポーリングされ、間にBufferedInputStreamをを入れてください。これは、ほぼ0あなたは、データ・レートを超過するリスクになるには、 CPU の負荷フォームの10%を削減しますブロックサイズのバイト数による。
import java.io.InputStream;
import java.io.IOException;
public class ThrottledInputStream extends InputStream {
private final InputStream rawStream;
private long totalBytesRead;
private long startTimeMillis;
private static final int BYTES_PER_KILOBYTE = 1024;
private static final int MILLIS_PER_SECOND = 1000;
private final int ratePerMillis;
public ThrottledInputStream(InputStream rawStream, int kBytesPersecond) {
this.rawStream = rawStream;
ratePerMillis = kBytesPersecond * BYTES_PER_KILOBYTE / MILLIS_PER_SECOND;
}
@Override
public int read() throws IOException {
if (startTimeMillis == 0) {
startTimeMillis = System.currentTimeMillis();
}
long now = System.currentTimeMillis();
long interval = now - startTimeMillis;
//see if we are too fast..
if (interval * ratePerMillis < totalBytesRead + 1) { //+1 because we are reading 1 byte
try {
final long sleepTime = ratePerMillis / (totalBytesRead + 1) - interval; // will most likely only be relevant on the first few passes
Thread.sleep(Math.max(1, sleepTime));
} catch (InterruptedException e) {//never realized what that is good for :)
}
}
totalBytesRead += 1;
return rawStream.read();
}
}
- その間!EOF
- System.currentTimeMillis() + 1000 (1秒)をlong変数に格納します
- 10K バッファを読み取る
- 保存された時間が経過したかどうかを確認する
- そうでない場合は、保存された時間 - 現在の時間の Thread.sleep()
提案されているように、別の InputStream を受け取る ThrottledInputStream を作成することは、優れた解決策となるでしょう。
それは、「一定の割合を超えない」ことを意味するのか、「一定の割合に近いままにする」ことを意味するのかによって少し異なります。
「超えない」という意味であれば、単純なループでそれを保証できます。
while not EOF do
read a buffer
Thread.wait(time)
write the buffer
od
待機時間はバッファのサイズに応じて決まります。バッファ サイズが 10K バイトの場合は、読み取りの間に 1 秒待機する必要があります。
それ以上に近づきたい場合は、タイマーを使用する必要があるかもしれません。
データを他のものに渡す速度が気になる場合は、読み取りを制御する代わりに、データをキューや循環バッファーなどのデータ構造に入れて、もう一方の端を制御します。定期的にデータを送信します。ただし、データセットのサイズなどによっては、リーダーがライターよりもはるかに高速である場合、メモリ制限に遭遇する可能性があるため、注意する必要があります。
あなたが飾るの流れに精通している必要があります。私は別のInputStream
を取り、流量を絞るInputStream
サブクラスを示唆しています。 (あなたがFileInputStream
をサブクラスでしたが、そのアプローチは非常にエラーが発生しやすいと柔軟性のないです。)
あなたの正確な実装は、あなたの正確な要件に依存します。一般的に、あなたの最後の読み取りが返された時間(System.nanoTime
を)注意したいと思うでしょう。現在の読み取りで、根本的なリード後、十分な時間までwait
は、転送されるデータの量のために合格しています。より洗練された実装では、(バッファの長さがゼロである場合にのみ、0の読み取り長を返す必要があることに注意してください)すぐ率のおもむくままと同じくらい多くのデータを(ほぼ)バッファと返すことがあります。
あなたはRateLimiterを使用することができます。そして、のInputStreamのリードの独自の実装を行います。この例は怒鳴る見ることができます。
public class InputStreamFlow extends InputStream {
private final InputStream inputStream;
private final RateLimiter maxBytesPerSecond;
public InputStreamFlow(InputStream inputStream, RateLimiter limiter) {
this.inputStream = inputStream;
this.maxBytesPerSecond = limiter;
}
@Override
public int read() throws IOException {
maxBytesPerSecond.acquire(1);
return (inputStream.read());
}
@Override
public int read(byte[] b) throws IOException {
maxBytesPerSecond.acquire(b.length);
return (inputStream.read(b));
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
maxBytesPerSecond.acquire(len);
return (inputStream.read(b,off, len));
}
}
あなたは1メガバイトで流れを制限したい場合は、/あなたは、このような入力ストリームを取得することができますよ。
final RateLimiter limiter = RateLimiter.create(RateLimiter.ONE_MB);
final InputStreamFlow inputStreamFlow = new InputStreamFlow(originalInputStream, limiter);