大きなデータファイルを1行ずつコピーする方法は?


9

35GBのCSVファイルがあります。各行を読み取り、条件に一致する場合はその行を新しいCSVに書き出します。

try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("source.csv"))) {
    try (BufferedReader br = Files.newBufferedReader(Paths.get("target.csv"))) {
        br.lines().parallel()
            .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
            .forEach(line -> {
                writer.write(line + "\n");
        });
    }
}

これには約かかります。7分。そのプロセスをさらにスピードアップすることは可能ですか?


1
はい、Javaからではなく、Linux / Windows / etcから直接実行することもできます。オペレーティング・システム。Javaは解釈され、それを使用すると常にオーバーヘッドが発生します。これに加えて、いいえ、私はそれをスピードアップする明白な方法はありません、そして35GBで7分は私には合理的に思えます。
Tim Biegeleisen

1
多分それを削除parallelすると速くなりますか?そして、それは周りの行をシャッフルしませんか?
Thilo

1
バッファサイズを設定できるコンストラクタを使用BufferedWriterして、自分で作成します。バッファサイズを大きく(または小さく)すると、違いが生じる可能性があります。バッファサイズをホストオペレーティングシステムのバッファサイズと一致させようとします。BufferedWriter
アブラ

5
@TimBiegeleisen:「Javaは解釈される」はせいぜい誤解を招くだけでなく、ほとんどの場合は間違いでも間違っています。はい、一部の最適化ではJVMの世界を離れる必要があるかもしれませんが、Javaでこれをより速く実行することは確実に実行可能です。
Joachim Sauer、

1
アプリケーションをプロファイルして、何かできるホットスポットがあるかどうかを確認する必要があります。生のIOについては多くのことはできません(デフォルトの8192バイトのバッファーは、セクターサイズなどが関係しているため、それほど悪くはありません)が、(内部で)と連携。
カヤマン

回答:


4

オプションの場合は、GZipInputStream / GZipOutputStreamを使用してディスクI / Oを最小限に抑えることができます。

Files.newBufferedReader / Writerは、デフォルトのバッファサイズである8 KBを使用すると思います。より大きなバッファーを試してみてください。

Unicodeの文字列に変換すると、速度が遅くなります(メモリを2倍使用します)。使用されるUTF-8は、StandardCharsets.ISO_8859_1ほど単純ではありません。

ほとんどの部分でバイトを操作でき、特定のCSVフィールドのみが文字列に変換するのが最善です。

メモリマップファイルが最も適切な場合があります。並列処理はファイル範囲によって使用され、ファイルを吐き出します。

try (FileChannel sourceChannel = new RandomAccessFile("source.csv","r").getChannel(); ...
MappedByteBuffer buf = sourceChannel.map(...);

これは少し多くのコードになり、で行が正しくなります(byte)'\n'が、過度に複雑になることはありません。


バイトの読み取りに関する問題は、実際には、行の先頭、特定の文字の部分文字列を評価し、行の残りの部分のみを出力ファイルに書き込む必要があることです。だから、おそらく行をバイトだけとして読むことはできませんか?
メンバーサウンド

GZipInputStream + GZipOutputStreamはRAMディスクで完全にインメモリをテストしました。パフォーマンスははるかに悪かった...
メンバーサウンド

1
Gzipの場合:遅いディスクではありません。はい、バイトはオプションです。改行、カンマ、タブ、セミコロンはすべてバイトとして処理でき、文字列よりもかなり高速です。UTF-8からUTF-16へのバイト、charからString、UTF-8からバイトへ。
Joop Eggen、

1
時間の経過とともにファイルのさまざまな部分をマッピングするだけです。限界に達したらMappedByteBuffer、最後の既知の良好な位置から新しいものを作成するだけです(FileChannel.map時間がかかります)。
Joachim Sauer

1
2019では、を使用する必要はありませんnew RandomAccessFile(…).getChannel()。だけを使用してくださいFileChannel.open(…)
Holger、

0

あなたはこれを試すことができます:

try (BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile), 1024 * 1024 * 64)) {
  try (BufferedReader br = new BufferedReader(new FileReader(sourceFile), 1024 * 1024 * 64)) {

1、2分節約できると思います。バッファーサイズを指定することで、私のマシンで約4分でテストを実行できます。

それはもっと速いのでしょうか?これを試して:

final char[] cbuf = new char[1024 * 1024 * 128];

try (Writer writer = new FileWriter(targetFile)) {
  try (Reader br = new FileReader(sourceFile)) {
    int cnt = 0;
    while ((cnt = br.read(cbuf)) > 0) {
      // add your code to process/split the buffer into lines.
      writer.write(cbuf, 0, cnt);
    }
  }
}

これにより、3〜4分節約できます。

それでも十分でない場合。(私があなたが質問をすることを私が推測する理由は、おそらくあなたはタスクを繰り返し実行する必要があるからです)。1分または2、3秒で完了したい場合。次に、データを処理してデータベースに保存してから、複数のサーバーでタスクを処理します。


最後の例として、cbufコンテンツを評価して、一部のみを書き出すにはどうすればよいですか?そして、一杯になったらバッファをリセットする必要がありますか?(バッファがいっぱいであることをどうやって知ることができますか?)
membersound

0

すべての提案のおかげで、私が思いついた最速の方法は、ライターをと交換することでしたBufferedOutputStream。これにより、約25%改善されました。

   try (BufferedReader reader = Files.newBufferedReader(Paths.get("sample.csv"))) {
        try (BufferedOutputStream writer = new BufferedOutputStream(Files.newOutputStream(Paths.get("target.csv")), 1024 * 16)) {
            reader.lines().parallel()
                    .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
                    .forEach(line -> {
                        writer.write((line + "\n").getBytes());
                    });
        }
    }

それでもBufferedReaderパフォーマンスBufferedInputStreamは私の場合よりも優れています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.