28

在以下情况下是否有任何理由更喜欢CharBuffera char[]

CharBuffer buf = CharBuffer.allocate(DEFAULT_BUFFER_SIZE);
while( in.read(buf) >= 0 ) {
  out.append( buf.flip() );
  buf.clear();
}

对比

char[] buf = new char[DEFAULT_BUFFER_SIZE];
int n;
while( (n = in.read(buf)) >= 0 ) {
  out.write( buf, 0, n );
}

inaReaderouta 在哪里Writer)?

4

7 回答 7

18

CharBuffer不,在这种情况下真的没有理由更喜欢 a 。

不过,总的来说,CharBuffer(和ByteBuffer)确实可以简化 API 并鼓励正确处理。如果您正在设计公共 API,那么绝对值得考虑使用面向缓冲区的 API。

于 2008-11-16T22:09:49.780 回答
7

我想对这个比较进行迷你基准测试。

下面是我写的课。

问题是我无法相信 CharBuffer 的表现如此糟糕。我做错了什么?

编辑:自从下面的第 11 条评论以来,我已经编辑了代码和输出时间,整体性能更好,但时间上仍然存在显着差异。我还尝试了注释中提到的 out2.append((CharBuffer)buff.flip()) 选项,但它比下面代码中使用的 write 选项慢得多。

结果:(以毫秒为单位的时间)
char[]:3411
CharBuffer:5653

public class CharBufferScratchBox
{
    public static void main(String[] args) throws Exception
    {
        // Some Setup Stuff
        String smallString =
                "1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000";

        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < 1000; i++)
        {
            stringBuilder.append(smallString);
        }
        String string = stringBuilder.toString();
        int DEFAULT_BUFFER_SIZE = 1000;
        int ITTERATIONS = 10000;

        // char[]
        StringReader in1 = null;
        StringWriter out1 = null;
        Date start = new Date();
        for (int i = 0; i < ITTERATIONS; i++)
        {
            in1 = new StringReader(string);
            out1 = new StringWriter(string.length());

            char[] buf = new char[DEFAULT_BUFFER_SIZE];
            int n;
            while ((n = in1.read(buf)) >= 0)
            {
                out1.write(
                        buf,
                        0,
                        n);
            }
        }
        Date done = new Date();
        System.out.println("char[]    : " + (done.getTime() - start.getTime()));

        // CharBuffer
        StringReader in2 = null;
        StringWriter out2 = null;
        start = new Date();
        CharBuffer buff = CharBuffer.allocate(DEFAULT_BUFFER_SIZE);
        for (int i = 0; i < ITTERATIONS; i++)
        {
            in2 = new StringReader(string);
            out2 = new StringWriter(string.length());
            int n;
            while ((n = in2.read(buff)) >= 0)
            {
                out2.write(
                        buff.array(),
                        0,
                        n);
                buff.clear();
            }
        }
        done = new Date();
        System.out.println("CharBuffer: " + (done.getTime() - start.getTime()));
    }
}
于 2008-11-17T09:42:28.897 回答
4

如果这是您对缓冲区所做的唯一事情,那么在这种情况下,数组可能是更好的选择。

CharBuffer 上面有很多额外的铬,但在这种情况下它们都无关紧要 - 只会减慢一点速度。

如果您需要使事情变得更复杂,您可以随时重构。

于 2008-11-16T21:16:17.250 回答
4

实际上,差异实际上小于 10%,而不是其他人报告的 30%。

为了读取和写入 5MB 文件 24 次,我使用 Profiler 获取了我的数字。他们平均为:

char[] = 4139 ms
CharBuffer = 4466 ms
ByteBuffer = 938 (direct) ms

个别测试多次青睐 CharBuffer。

我还尝试用内存 IO 替换基于文件的 IO,性能相似。如果您尝试从一个本地流传输到另一个,那么最好使用“直接”ByteBuffer。

由于性能差异不到 10%,在实践中,我更喜欢 CharBuffer。它的语法更清晰,无关变量更少,您可以对其进行更直接的操作(即任何需要 CharSequence 的操作)。

基准在下面...有点错误,因为 BufferedReader 分配在测试方法内部而不是外部...但是,下面的示例允许您隔离 IO 时间并消除字符串或字节流调整其内部大小等因素内存缓冲区等

public static void main(String[] args) throws Exception {
    File f = getBytes(5000000);
    System.out.println(f.getAbsolutePath());
    try {
        System.gc();
        List<Main> impls = new java.util.ArrayList<Main>();
        impls.add(new CharArrayImpl());
        //impls.add(new CharArrayNoBuffImpl());
        impls.add(new CharBufferImpl());
        //impls.add(new CharBufferNoBuffImpl());
        impls.add(new ByteBufferDirectImpl());
        //impls.add(new CharBufferDirectImpl());
        for (int i = 0; i < 25; i++) {
            for (Main impl : impls) {
                test(f, impl);
            }
            System.out.println("-----");
            if(i==0)
                continue; //reset profiler
        }
        System.gc();
        System.out.println("Finished");
        return;
    } finally {
        f.delete();
    }
}
static int BUFFER_SIZE = 1000;

static File getBytes(int size) throws IOException {
    File f = File.createTempFile("input", ".txt");
    FileWriter writer = new FileWriter(f);
    Random r = new Random();
    for (int i = 0; i < size; i++) {
        writer.write(Integer.toString(5));
    }
    writer.close();
    return f;
}

static void test(File f, Main impl) throws IOException {
    InputStream in = new FileInputStream(f);
    File fout = File.createTempFile("output", ".txt");
    try {
        OutputStream out = new FileOutputStream(fout, false);
        try {
            long start = System.currentTimeMillis();
            impl.runTest(in, out);
            long end = System.currentTimeMillis();
            System.out.println(impl.getClass().getName() + " = " + (end - start) + "ms");
        } finally {
            out.close();
        }
    } finally {
        fout.delete();
        in.close();
    }
}

public abstract void runTest(InputStream ins, OutputStream outs) throws IOException;

public static class CharArrayImpl extends Main {

    char[] buff = new char[BUFFER_SIZE];

    public void runTest(InputStream ins, OutputStream outs) throws IOException {
        Reader in = new BufferedReader(new InputStreamReader(ins));
        Writer out = new BufferedWriter(new OutputStreamWriter(outs));
        int n;
        while ((n = in.read(buff)) >= 0) {
            out.write(buff, 0, n);
        }
    }
}

public static class CharBufferImpl extends Main {

    CharBuffer buff = CharBuffer.allocate(BUFFER_SIZE);

    public void runTest(InputStream ins, OutputStream outs) throws IOException {
        Reader in = new BufferedReader(new InputStreamReader(ins));
        Writer out = new BufferedWriter(new OutputStreamWriter(outs));
        int n;
        while ((n = in.read(buff)) >= 0) {
            buff.flip();
            out.append(buff);
            buff.clear();
        }
    }
}

public static class ByteBufferDirectImpl extends Main {

    ByteBuffer buff = ByteBuffer.allocateDirect(BUFFER_SIZE * 2);

    public void runTest(InputStream ins, OutputStream outs) throws IOException {
        ReadableByteChannel in = Channels.newChannel(ins);
        WritableByteChannel out = Channels.newChannel(outs);
        int n;
        while ((n = in.read(buff)) >= 0) {
            buff.flip();
            out.write(buff);
            buff.clear();
        }
    }
}
于 2008-11-20T01:19:34.357 回答
2

我认为 CharBuffer 和 ByteBuffer(以及任何其他 xBuffer)是为了可重用性,所以你可以 buf.clear() 它们而不是每次都重新分配

如果你不重复使用它们,你就没有充分利用它们的潜力,而且会增加额外的开销。但是,如果您打算扩展此功能,那么将它们保留在那里可能是个好主意

于 2008-11-17T01:56:17.823 回答
1

CharBuffer 版本稍微不那么复杂(少一个变量),封装了缓冲区大小处理并使用标准 API。一般来说,我更喜欢这个。

然而,至少在某些情况下,仍然有一个很好的理由更喜欢阵列版本。CharBuffer 仅在 Java 1.4 中引入,因此如果您要部署到早期版本,则不能使用 Charbuffer(除非您自己滚动/使用反向端口)。

PS 如果您使用反向移植,请记住在赶上包含反向移植代码的“真实”版本的版本时将其删除。

于 2008-11-17T01:43:07.150 回答
1

您应该避免CharBuffer在最近的 Java 版本中,#subsequence(). 您无法从缓冲区的后半部分获取子序列,因为实现混淆了capacityremaining。我观察到 java 6-0-11 和 6-0-12 中的错误。

于 2009-02-12T09:50:45.923 回答