CharBuffer против char [] - PullRequest
       15

CharBuffer против char []

26 голосов
/ 16 ноября 2008

Есть ли основания предпочитать CharBuffer вместо 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 );
}

(где in - это Reader и out в Writer)?

Ответы [ 7 ]

17 голосов
/ 17 ноября 2008

Нет, на самом деле нет причин предпочитать CharBuffer в этом случае.

В целом, однако, CharBufferByteBuffer) действительно могут упростить API и способствовать правильной обработке. Если вы разрабатывали публичный API, определенно стоит подумать о API, ориентированном на буфер.

6 голосов
/ 17 ноября 2008

Я хотел мини-эталон этого сравнения.

Ниже приведен класс, который я написал.

Дело в том, что я не могу поверить, что CharBuffer работал так плохо. Что я не так понял?

РЕДАКТИРОВАТЬ: После 11-го комментария, приведенного ниже, я отредактировал код и время вывода, повысила производительность по всем параметрам, но все еще существенная разница во времени. Я также попробовал опцию out2.append ((CharBuffer) buff.flip ()), упомянутую в комментариях, но она была намного медленнее, чем опция записи, использованная в приведенном ниже коде.

Результаты: (время в мс)
символ []: 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()));
    }
}
4 голосов
/ 17 ноября 2008

Если это единственное, что вы делаете с буфером, то массив, вероятно, является лучшим выбором в этом случае.

В CharBuffer много лишнего хрома, но в данном случае он не имеет никакого отношения к делу - он только немного замедлит работу.

Вы всегда можете выполнить рефакторинг позже, если вам нужно все усложнить.

3 голосов
/ 20 ноября 2008

На практике разница составляет <10%, а не 30%, как сообщают другие. </p>

Чтобы прочитать и записать файл размером 5 МБ 24 раза, мои номера взяты с помощью Profiler. Они были в среднем:

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

Отдельные тесты пару раз отдали предпочтение CharBuffer.

Я также пытался заменить файловый ввод-вывод на In-Memory IO, и производительность была схожей. Если вы пытаетесь перенести из одного собственного потока в другой, то лучше использовать «прямой» ByteBuffer.

На практике с разницей в производительности менее 10% я бы предпочел CharBuffer. Его синтаксис более понятен, в нем меньше посторонних переменных, и вы можете выполнять с ним более прямые манипуляции (т. Е. Все, что требует CharSequence).

Бенчмарк ниже ... он немного неправильный, так как BufferedReader размещен внутри метода test, а не снаружи ... однако, приведенный ниже пример позволяет изолировать время ввода-вывода и устранить такие факторы, как строка или поток байтов изменение размера буфера внутренней памяти и т. д.

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();
        }
    }
}
2 голосов
/ 17 ноября 2008

Я думаю, что CharBuffer и ByteBuffer (как и любой другой xBuffer) были предназначены для повторного использования, так что вы можете использовать buf.clear () вместо того, чтобы каждый раз проходить перераспределение

Если вы не используете их повторно, вы не используете их полный потенциал, и это приведет к дополнительным накладным расходам. Однако, если вы планируете масштабировать эту функцию, неплохо было бы сохранить их там

1 голос
/ 12 февраля 2009

Вам следует избегать CharBuffer в последних версиях Java, в #subsequence() есть ошибка. Вы не можете получить подпоследовательность из второй половины буфера, так как реализация путает capacity и remaining. Я наблюдал ошибку в Java 6-0-11 и 6-0-12.

1 голос
/ 17 ноября 2008

Версия CharBuffer немного менее сложна (на одну переменную меньше), инкапсулирует обработку размера буфера и использует стандартный API. Вообще я бы предпочел это.

Однако есть еще одна веская причина, чтобы предпочесть версию массива, по крайней мере, в некоторых случаях. CharBuffer был представлен только в Java 1.4, поэтому, если вы развертываете более раннюю версию, вы не можете использовать Charbuffer (если только вы не используете свою роль / не используете бэкпорт).

P.S. Если вы используете бэкпорт, не забудьте удалить его, как только догоните версию, содержащую "настоящую" версию бэкпортированного кода.

...