Java中的解码字符:为什么读者比使用缓冲区更快



我正在尝试几种方法将文件的字节解码为字符。

使用 java.io.io.reader channels.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才能完成。第二种方法始终需要700毫秒。您是否知道为什么读者的方法要快得多?

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

基准测试是在Windows 7上执行的,JDK7_07。

用于比较您可以尝试。

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指出的那样,您正在更改许多事情,您需要从最简单的可比较示例开始,看看每个元素添加了多少差异。

这是第三个实现,不使用映射的缓冲区。在相同的条件下,它在220毫秒内持续运行。我的计算机上的默认字符是" Windows-1252",如果我强制更简单的ISO-8859-1" charset解码甚至更快(约150ms)。

看起来像本机的用法诸如映射缓冲区之类的功能实际上会损害性能(对于这种用例)。同样有趣的是,如果我分配直接缓冲区而不是堆缓冲区(查看评论的线路),那么性能会降低(然后运行大约需要400ms)。

到目前为止的答案似乎是:在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();
}

最新更新