Как поместить данные из OutputStream в ByteBuffer?

В Java мне нужно поместить содержимое из OutputStream (я сам заполняю данные в этот поток) в ByteBuffer. Как это сделать простым способом?


person Rasto    schedule 26.04.2010    source источник
comment
Как правило, вы не можете читать напрямую из выходного потока. Если вы сами заполняете данные, почему вы не можете заполнить их и в ByteBuffer?   -  person Eyal Schneider    schedule 27.04.2010
comment
Можно читать из него косвенно и напрямую, если вы создаете класс, который наследует форму OutputStream и позволяет читать из него напрямую. Я не сам заполнил данные, это сделал какой-то фреймворк (и код фреймворка трогать не хочу). Смотрите мой другой комментарий и ответы.   -  person Rasto    schedule 01.05.2010


Ответы (6)


Вы можете создать ByteArrayOutputStream и писать в него, а также извлекать содержимое как byte[] с помощью toByteArray(). Затем ByteBuffer.wrap(byte []) создаст ByteBuffer с содержимым массива выходных байтов.

person DJClayworth    schedule 26.04.2010
comment
Это именно то, что я сделал, прежде чем я прочитал ваш комментарий. Так что я могу подтвердить, что это работает :)) - person Rasto; 27.04.2010
comment
К сожалению, ByteArrayOutputStream#toByteArray() создает копию лежащего в основе массива байтов, так что, хотя этот рецепт прост, он не так эффективен, как хотелось бы. Однако, поскольку емкость ByteBuffer фиксирована, желание записать произвольное количество данных через OutputStream в ByteBuffer непримиримо. - person seh; 02.02.2012
comment
@seh Так не должно быть - смотрите мой ответ. - person mark; 28.03.2013

Существует более эффективный вариант ответа @DJClayworth.

Как правильно заметил @seh, ByteArrayOutputStream.toByteArray() возвращает копию резервного объекта byte[], что может быть неэффективным. Однако и вспомогательный объект byte[], и счетчик байтов являются защищенными членами класса ByteArrayOutputStream. Следовательно, вы можете создать свой собственный вариант ByteArrayOutputStream, раскрывая их напрямую:

public class MyByteArrayOutputStream extends ByteArrayOutputStream {
  public MyByteArrayOutputStream() {
  }

  public MyByteArrayOutputStream(int size) {
    super(size);
  }

  public int getCount() {
    return count;
  }

  public byte[] getBuf() {
    return buf;
  }
}

Использовать этот класс легко:

MyByteArrayOutputStream out = new MyByteArrayOutputStream();
fillTheOutputStream(out);
return new ByteArrayInputStream(out.getBuf(), 0, out.getCount());

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

person mark    schedule 28.03.2013
comment
Хороший вопрос, @mark. У меня есть такой класс в нескольких моих Java-проектах, обычно ограниченный как частный класс пакета, называемый PromiscuousByteArrayOutputStream. Имя намеренно длинное и зловещее. - person seh; 28.03.2013
comment
getCount() не требуется, так как ByteArrayOutputStream.size() возвращает count. - person JT.; 24.12.2014

Хотя вышеупомянутые ответы решают вашу проблему, ни один из них не является эффективным, как вы ожидаете от NIO. ByteArrayOutputStream или MyByteArrayOutputStream сначала записывают данные в память кучи Java, а затем копируют их в ByteBuffer, что сильно влияет на производительность.

Эффективной реализацией будет самостоятельное написание класса ByteBufferOutputStream. На самом деле это довольно легко сделать. Вы должны просто предоставить метод write(). См. эту ссылку для ByteBufferInputStream.

person RoboAlex    schedule 09.05.2014
comment
поскольку нам может потребоваться повторно использовать существующий ByteBuffer (и его базовый массив) для OutputStream вместо выделения нового массива для получения нового ByteBuffer, я считаю этот ответ более приемлемым. - person Ambling; 06.06.2017

Попробуйте использовать PipedOutputStream вместо OutputStream. Затем вы можете подключить PipedInputStream для чтения данных обратно из PipedOutputStream.

person Marcus Adams    schedule 26.04.2010
comment
Выглядит хорошо. То же самое можно сделать с ByteArrayOutputStream, вызвав его метод newInputStream. Однако есть более прямое решение - обернуть массив, который резервирует ByteArrayOutputStream, напрямую в ByteBuffer. Это то, что я сделал. - person Rasto; 27.04.2010

Вы говорите, что сами пишете в этот поток? Если это так, возможно, вы могли бы реализовать свой собственный ByteBufferOutputStream и подключить его.

Базовый класс будет выглядеть так:

public class ByteBufferOutputStream extends OutputStream {
    //protected WritableByteChannel wbc; //if you need to write directly to a channel
    protected static int bs = 2 * 1024 * 1024; //2MB buffer size, change as needed
    protected ByteBuffer bb = ByteBuffer.allocate(bs);

    public ByteBufferOutputStream(...) {
        //wbc = ... //again for writing to a channel
    }

    @Override
    public void write(int i) throws IOException {
        if (!bb.hasRemaining()) flush();
        byte b = (byte) i;
        bb.put(b);
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        if (bb.remaining() < len) flush();
        bb.put(b, off, len);
    }

    /* do something with the buffer when it's full (perhaps write to channel?)
    @Override
    public void flush() throws IOException {
        bb.flip();
        wbc.write(bb);
        bb.clear();
    }

    @Override
    public void close() throws IOException {
        flush();
        wbc.close();
    }
    /*
}
person MeetTitan    schedule 06.11.2018

// доступ к защищенному члену buf и count из класса расширения

class ByteArrayOutputStream2ByteBuffer extends ByteArrayOutputStream {
    public ByteBuffer toByteBuffer() {
        return ByteBuffer.wrap(buf, 0, count);
    }
}
person Yessy    schedule 30.03.2020