生徒の記録を含むサイズ 2GB のファイルがあります。各レコードの特定の属性に基づいて学生を検索し、結果を含む新しいファイルを作成する必要があります。フィルタリングされた学生の順序は、元のファイルと同じである必要があります。Java IO API とスレッドを使用して、メモリの問題なしでこれを行う効率的で最速の方法は何ですか? JVM の最大ヒープ サイズは 512MB に設定されています。
4 に答える
どんなファイル?CSVのようなテキストベース?
最も簡単な方法は、grep が行うようなことを行うことです。ファイルを 1 行ずつ読み取り、行を解析し、フィルター基準をチェックし、一致する場合は結果行を出力し、ファイルが完了するまで次の行に進みます。同時にロードされるのは現在の行 (または少し大きいバッファー) だけなので、これは非常にメモリ効率が良いです。プロセスは、ファイル全体を 1 回だけ読み取る必要があります。
複数のスレッドがあまり役に立たないと思います。プロセスはとにかく I/O バウンドのように見えるため、複数のスレッドで同じファイルを読み込もうとしても、おそらくスループットは向上しません。
これを頻繁に行う必要があり、毎回ファイルを調べるのが遅すぎる場合は、ある種のインデックスを作成する必要があります。これを行う最も簡単な方法は、最初にファイルを DB (SQLite や HSQL などの組み込み DB である可能性があります) にインポートすることです。
退屈なほど単純な方法が必要なものに対して機能しないことがわかるまで、これを過度に複雑にすることはありません。基本的に、次のことを行う必要があります。
- 入力ストリームを 2GB ファイルに開き、バッファすることを忘れない (例: BufferedInputStream でラップする)
- 作成するフィルター済みファイルへの出力ストリームを開く
- 入力ストリームから最初のレコードを読み取り、属性を調べて「必要」かどうかを判断します。その場合は、出力ファイルに書き込みます
- 残りのレコードについて繰り返す
非常に控えめなハードウェアを備えた私のテスト システムの 1 つで、箱から出してすぐに FileInputStream を囲む BufferedInputStream は、25 秒で約 500 MB を読み取りました。つまり、2 GB のファイルを処理するのにおそらく 2 分未満でした。 (詳細については、作成したBufferedInputStream のタイミングを参照してください)。最先端のハードウェアを使用すれば、時間が半分になる可能性は十分にあると思います。
2/3 分を短縮するために多くの労力を費やす必要があるか、それとも実行を待っている間にほんの少しだけ行く必要があるかは、要件に応じて決定する必要があります。同じデータセットに対して多くの異なる処理を実行する必要がない限り、データベースオプションはあまり役に立たないと思います(これには、自動的にデータベースを意味しない他のソリューションがあります)。
- ファイルの 2GB は巨大です。データベースを使用する必要があります。
- 本当にJava I/O APIを使用したい場合は、次のことを試してください: Java で大きなデータ ファイルを効率的に処理することと、次のことを試してください: Java I/O パフォーマンスのチューニング
メモリ マップされたファイルを使用する必要があると思います。これは、大きなファイルを小さなメモリにマップするのに役立ちます。これは仮想メモリのように機能し、パフォーマンスに関する限り、マップされたファイルはストリームの書き込み/読み取りよりも高速です。