特定の速度で長いファイルを読み取る方法に関する記事/アルゴリズムはありますか?
読み取りの発行中に 10 KB/秒を渡したくないとします。
ThrottledInputStream を作成することによる簡単な解決策。
これは次のように使用する必要があります。
final InputStream slowIS = new ThrottledInputStream(new BufferedInputStream(new FileInputStream("c:\\file.txt"),8000),300);
300 は 1 秒あたりのキロバイト数です。8000 は BufferedInputStream のブロック サイズです。
もちろん、これは read(byte b[], int off, int len) を実装することで一般化する必要があります。これにより、大量の System.currentTimeMillis() 呼び出しが不要になります。System.currentTimeMillis() は、バイト読み取りごとに 1 回呼び出されるため、多少のオーバーヘッドが発生する可能性があります。System.currentTimeMillis() を呼び出さずに安全に読み取ることができるバイト数を格納することも可能です。
間に BufferedInputStream を配置してください。そうしないと、FileInputStream はブロックではなく単一バイトでポーリングされます。これにより、CPU負荷が 10% からほぼ 0 に減少します。ブロック サイズのバイト数だけデータ レートを超えるリスクがあります。
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();
}
}
大まかな解決策は、一度にチャンクを読み取ってから、たとえば 10k をスリープしてから 1 秒スリープすることです。しかし、私が尋ねなければならない最初の質問は、なぜですか? 考えられる答えがいくつかあります。
私の提案は、読み取りレベルで制御しないことです。それはちょっと面倒で不正確です。代わりに、作業終了時に制御します。Java には、これに対処するための優れた同時実行ツールが多数あります。これを行うには、いくつかの代替方法があります。
私は、この種の問題を解決するために生産者と消費者のパターンを使用するのが好きです。レポートスレッドなどを使用して進行状況を監視できる優れたオプションを提供し、非常にクリーンなソリューションになる可能性があります.
(1) と (2) の両方に必要なスロットリングには、 ArrayBlockingQueueのようなものを使用できます。容量が限られているため、キューがいっぱいになるとリーダーは最終的にブロックされるため、すぐにいっぱいになることはありません。ワーカー (コンシューマー) は、非常に高速に動作するように制御して、レート カバーを調整することもできます (2)。
提案されているように別の InputStream を取る ThrottledInputStream を作成すると、良い解決策になります。
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 MB/s で制限したい場合は、次のように入力ストリームを取得できます。
final RateLimiter limiter = RateLimiter.create(RateLimiter.ONE_MB);
final InputStreamFlow inputStreamFlow = new InputStreamFlow(originalInputStream, limiter);
Java I/O を使用したことがあれば、ストリームの装飾に精通しているはずです。InputStream
別のサブクラスを使用しInputStream
てフロー レートを調整することをお勧めします。(サブクラス化することはできますがFileInputStream
、そのアプローチは非常にエラーが発生しやすく、柔軟性に欠けます。)
正確な実装は、正確な要件によって異なります。通常、最後の読み取りが返された時間 ( ) に注意する必要がありますSystem.nanoTime
。現在の読み取りで、基になる読み取りの後、wait
転送されたデータ量に対して十分な時間が経過するまで。より洗練された実装では、レートが指示するのと同じ量のデータのみをバッファリングして (ほぼ) すぐに返すことができます (バッファの長さがゼロの場合は、読み取り長さ 0 のみを返すように注意してください)。
「一定のレートを超えないようにする」という意味か、「一定のレートに近づける」という意味かによって、少し異なります。
「超えない」ことを意味する場合は、単純なループでそれを保証できます。
while not EOF do
read a buffer
Thread.wait(time)
write the buffer
od
待機時間は、バッファのサイズの単純な関数です。バッファー サイズが 10K バイトの場合、読み取りの間に 1 秒待機する必要があります。
それ以上近づきたい場合は、おそらくタイマーを使用する必要があります。
データを別のものに渡す速度が気になる場合は、読み取りを制御するのではなく、データをキューや循環バッファーなどのデータ構造に入れ、もう一方の端を制御します。定期的にデータを送信します。ただし、データセットのサイズなどによっては、リーダーがライターよりもはるかに高速な場合、メモリの制限に遭遇する可能性があるため、注意が必要です。