8

文字列を含む巨大なファイルを新しい(より小さい)ファイルのセットに分割し、nio2 を使用しようとしました。

ファイル全体をメモリにロードしたくないので、BufferedReader で試してみました。

小さいテキスト ファイルは、テキスト行の数によって制限する必要があります。

解決策は機能しますが、java 8 (stream()-api を使用したラムダなど) と nio2 を使用することで、パフォーマンスが向上する解決策を誰かが知っているかどうかを尋ねたいと思います。

public void splitTextFiles(Path bigFile, int maxRows) throws IOException{

        int i = 1;
        try(BufferedReader reader = Files.newBufferedReader(bigFile)){
            String line = null;
            int lineNum = 1;

            Path splitFile = Paths.get(i + "split.txt");
            BufferedWriter writer = Files.newBufferedWriter(splitFile, StandardOpenOption.CREATE);

            while ((line = reader.readLine()) != null) {

                if(lineNum > maxRows){
                    writer.close();
                    lineNum = 1;
                    i++;
                    splitFile = Paths.get(i + "split.txt");
                    writer = Files.newBufferedWriter(splitFile, StandardOpenOption.CREATE);
                }

                writer.append(line);
                writer.newLine();
                lineNum++;
            }

            writer.close();
        }
}
4

2 に答える 2

4

/とそのサブクラスを直接使用する場合と、 の/ファクトリ メソッドを使用する場合の違いに注意してください。前者の場合、明示的な文字セットが指定されていない場合はシステムのデフォルトのエンコーディングが使用されますが、後者の場合は常にデフォルトで. そのため、目的の文字セットを常に指定することを強くお勧めします。たとえそれがorであっても、意図を文書化し、 orを作成するさまざまな方法を切り替えた場合の驚きを避けるためです。 InputStreamReaderOutputStreamWriterReaderWriter FilesUTF-8Charset.defaultCharset()StandardCharsets.UTF_8ReaderWriter


行の境界で分割したい場合、ファイルの内容を調べる方法はありません。したがって、マージするときのように最適化することはできません。

移植性を犠牲にしても構わないと思っている場合は、いくつかの最適化を試すことができます。ほとんどのシングルバイトエンコーディングの場合と同様に、文字セットエンコーディングが明確にマップ'\n'されることがわかっている場合は、バイトレベルで改行をスキャンして、分割のファイル位置を取得し、アプリケーションからのデータ転送を回避できますI/O システムに。(byte)'\n'UTF-8

public void splitTextFiles(Path bigFile, int maxRows) throws IOException {
    MappedByteBuffer bb;
    try(FileChannel in = FileChannel.open(bigFile, READ)) {
        bb=in.map(FileChannel.MapMode.READ_ONLY, 0, in.size());
    }
    for(int start=0, pos=0, end=bb.remaining(), i=1, lineNum=1; pos<end; lineNum++) {
        while(pos<end && bb.get(pos++)!='\n');
        if(lineNum < maxRows && pos<end) continue;
        Path splitFile = Paths.get(i++ + "split.txt");
        // if you want to overwrite existing files use CREATE, TRUNCATE_EXISTING
        try(FileChannel out = FileChannel.open(splitFile, CREATE_NEW, WRITE)) {
            bb.position(start).limit(pos);
            while(bb.hasRemaining()) out.write(bb);
            bb.clear();
            start=pos;
            lineNum = 0;
        }
    }
}

欠点は、古い MacOS9 で使用されていた行末記号としてlone をサポートしないのとは異なり、UTF-16or and のようなエンコーディングでは機能しないことです。EBCDICBufferedReader.readLine()'\r'

さらに、2GB 未満のファイルのみをサポートします。仮想アドレス空間が限られているため、32 ビット JVM では制限がさらに小さくなる可能性があります。制限より大きいファイルの場合、ソース ファイルのチャンクとmapそれらを次々と繰り返し処理する必要があります。

これらの問題は修正できますが、このアプローチの複雑さが増します。私のマシンでは速度の向上が約 15% にすぎず (ここでは I/O が支配的であるため、これ以上の向上は期待していませんでした)、複雑さが増すとさらに小さくなるという事実を考えると、それだけの価値があるとは思いません。


肝心なのは、このタスクにはReader/アプローチで十分ですが、操作に使用される にWriter注意する必要があるということです。Charset

于 2014-08-29T10:55:24.997 に答える