4

ファイルのバイトを文字にデコードするいくつかの方法を試しています。

java.io.ReaderChannels.newReader(...)を使用する

public static void decodeWithReader() throws Exception {
    FileInputStream fis = new FileInputStream(FILE);
    FileChannel channel = fis.getChannel();
    CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
    Reader reader = Channels.newReader(channel, decoder, -1);

    final char[] buffer = new char[4096];
    for(;;) {
        if(-1 == reader.read(buffer)) {
            break;
        }
    }

    fis.close();
}

バッファとデコーダを手動で使用する:

public static void readWithBuffers() throws Exception {
    FileInputStream fis = new FileInputStream(FILE);
    FileChannel channel = fis.getChannel();
    CharsetDecoder decoder = Charset.defaultCharset().newDecoder();

    final long fileLength = channel.size();
    long position = 0;
    final int bufferSize = 1024 * 1024;   // 1MB

    CharBuffer cbuf = CharBuffer.allocate(4096);

    while(position < fileLength) {
        MappedByteBuffer bbuf = channel.map(MapMode.READ_ONLY, position, Math.min(bufferSize, fileLength - position));
        for(;;) {
            CoderResult res = decoder.decode(bbuf, cbuf, false);

            if(CoderResult.OVERFLOW == res) {
                cbuf.clear();
            } else if (CoderResult.UNDERFLOW == res) {
                break;
            }
        }
        position += bbuf.position();
    }

    fis.close();
}

200MBのテキストファイルの場合、最初のアプローチは一貫して完了するのに300msかかります。2番目のアプローチは一貫して700msかかります。読者のアプローチがなぜこれほど速いのか、あなたは何か考えがありますか?

別の実装でさらに高速に実行できますか?

ベンチマークは、Windows7およびJDK7_07で実行されます。

4

2 に答える 2

2

比較のために試してみてください。

public static void readWithBuffersISO_8859_1() throws Exception {
    FileInputStream fis = new FileInputStream(FILE);
    FileChannel channel = fis.getChannel();
    MappedByteBuffer bbuf = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
    while(bbuf.remaining()>0) {
        char ch = (char)(bbuf.get() & 0xFF);
    }
    fis.close();
}

これはISO-8859-1を前提としています。最高速度が必要な場合は、オプションとしてテキストをバイナリ形式のように扱うと役立ちます。

@EJPが指摘しているように、多くのことを一度に変更しているので、最も単純な比較可能な例から始めて、各要素がどの程度の違いを追加するかを確認する必要があります。

于 2012-10-29T11:38:04.247 に答える
2

これは、マップされたバッファを使用しない3番目の実装です。以前と同じ条件で、220msで一貫して動作します。私のマシンのデフォルトの文字セットは「windows-1252」です。より単純な「ISO-8859-1」文字セットを強制すると、デコードはさらに高速になります(約150ms)。

マップされたバッファなどのネイティブ機能を使用すると、実際にパフォーマンスが低下するようです(このユースケースの場合)。また興味深いことに、ヒープバッファの代わりに直接バッファを割り当てると(コメント行を見てください)、パフォーマンスが低下します(実行には約400msかかります)。

これまでのところ、答えは次のように思われます。Javaで文字をできるだけ速くデコードするには(1つの文字セットの使用を強制できない場合)、デコーダーを手動で使用する、ヒープバッファーを使用してデコードループを作成する、マップされたバッファーを使用しない、またはネイティブのものでさえ。なぜそうなのか、私にはよくわからないことを認めなければなりません。

public static void readWithBuffers() throws Exception {
    FileInputStream fis = new FileInputStream(FILE);
    FileChannel channel = fis.getChannel();
    CharsetDecoder decoder = Charset.defaultCharset().newDecoder();

    // CharsetDecoder decoder = Charset.forName("ISO-8859-1").newDecoder();

    ByteBuffer bbuf = ByteBuffer.allocate(4096);
    // ByteBuffer bbuf = ByteBuffer.allocateDirect(4096);
    CharBuffer cbuf = CharBuffer.allocate(4096);
    // CharBuffer cbuf = ByteBuffer.allocateDirect(2 * 4096).asCharBuffer();

    for(;;) {
        if(-1 == channel.read(bbuf)) {
            decoder.decode(bbuf, cbuf, true);
            decoder.flush(cbuf);
            break;
        }
        bbuf.flip();

        CoderResult res = decoder.decode(bbuf, cbuf, false);
        if(CoderResult.OVERFLOW == res) {
            cbuf.clear();
        } else if (CoderResult.UNDERFLOW == res) {
            bbuf.compact();
        }
    }

    fis.close();
}
于 2012-10-29T13:15:43.960 に答える