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 的文本文件,第一种方法始终需要 300 毫秒才能完成。第二种方法始终需要 700 毫秒。你知道为什么阅读器的方法要快得多吗?

它可以通过另一个实现运行得更快吗?

基准测试在 Windows 7 和 JDK7_07 上执行。

4

2 回答 2

2

这是不使用映射缓冲区的第三种实现。在与以前相同的条件下,它始终在 220 毫秒内运行。我机器上的默认字符集是“windows-1252”,如果我强制使用更简单的“ISO-8859-1”字符集,解码速度会更快(大约 150 毫秒)。

看起来像映射缓冲区这样的原生特性的使用实际上会损害性能(对于这个用例)。同样有趣的是,如果我分配直接缓冲区而不是堆缓冲区(查看注释行),那么性能会降低(然后运行大约需要 400 毫秒)。

到目前为止,答案似乎是:在 Java 中尽可能快地解码字符(前提是您不能强制使用一个字符集),手动使用解码器,使用堆缓冲区编写解码循环,不要使用映射缓冲区或甚至是本土的。我不得不承认我真的不知道为什么会这样。

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 回答
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 回答