Самый эффективный способ создания InputStream из OutputStream

86

На этой странице: http://blog.ostermiller.org/convert-java-outputstream-inputstream описывается, как создать InputStream из OutputStream:

new ByteArrayInputStream(out.toByteArray())

Другие альтернативы - использование PipedStreams и новых потоков, что является громоздким.

Мне не нравится идея копирования большого количества мегабайт в новый массив байтов в памяти. Есть ли библиотека, которая делает это более эффективно?

РЕДАКТИРОВАТЬ:

По совету Лоуренса Гонсалвеса я попробовал PipedStreams, и оказалось, что с ними не так уж сложно справиться. Вот пример кода в clojure:

(defn #^PipedInputStream create-pdf-stream [pdf-info]
  (let [in-stream (new PipedInputStream)
        out-stream (PipedOutputStream. in-stream)]
    (.start (Thread. #(;Here you write into out-stream)))
    in-stream))
Вагиф Верди
источник

Ответы:

73

Если вы не хотите сразу копировать все данные в буфер в памяти, вам понадобится код, использующий OutputStream (производитель), и код, использующий InputStream (потребитель ) либо поочередно работают в одном потоке, либо работают одновременно в двух отдельных потоках. Их работа в одном потоке, вероятно, намного сложнее, чем использование двух отдельных потоков, гораздо более подвержена ошибкам (вам нужно убедиться, что потребитель никогда не блокирует ожидание ввода, иначе вы фактически заблокируете) и потребует когда производитель и потребитель работают в одном и том же цикле, что кажется слишком тесно связанным.

Так что используйте второй поток. Это действительно не так уж сложно. На странице, на которую вы ссылаетесь, был прекрасный пример:

  PipedInputStream in = new PipedInputStream();
  PipedOutputStream out = new PipedOutputStream(in);
  new Thread(
    new Runnable(){
      public void run(){
        class1.putDataOnOutputStream(out);
      }
    }
  ).start();
  class2.processDataFromInputStream(in);
Лоуренс Гонсалвес
источник
Я думаю, вам также нужно создать новый PipedInputStream для каждого потребительского потока. Если вы читаете из Pipe из другого потока, это выдаст вам ошибку.
Денис Тульский, 04
@Lawrence: Я не понимаю вашего обоснования использования двух потоков ... ЕСЛИ это не требование, чтобы все символы, считанные из InputStream, своевременно записывались в OutputStream.
Stephen C
8
Стивен: вы не можете ничего прочитать, пока это не будет написано. Таким образом, имея только один поток, вам либо нужно сначала все записать (создавая большой массив в памяти, которого хотел избежать Вагиф), либо вам нужно их чередовать, очень осторожно, чтобы читатель никогда не блокировал ожидание ввода (потому что, если он это сделает , писатель тоже никогда не сможет выполнить).
Лоуренс Гонсалвес,
1
безопасно ли это предложение для использования в среде JEE, где контейнер, вероятно, выполняет множество собственных потоков?
Toskan
2
@Toskan, если new Threadпо какой-либо причине не подходит для вашего контейнера, посмотрите, есть ли пул потоков, который вы можете использовать.
Лоуренс Гонсалвес,
14

Существует еще одна библиотека с открытым исходным кодом, называемая EasyStream, которая прозрачно работает с конвейерами и потоками. Это не сложно, если все идет хорошо. Проблемы возникают, когда (глядя на пример Лоуренса Гонсалвеса)

class1.putDataOnOutputStream (выход);

Выдает исключение. В этом примере поток просто завершается, и исключение теряется, а внешний поток InputStreamможет быть усечен.

Easystream занимается распространением исключений и другими неприятными проблемами, которые я отлаживал около года. (Я являюсь хранителем библиотеки: очевидно, мое решение - лучшее;)) Вот пример того, как его использовать:

final InputStreamFromOutputStream<String> isos = new InputStreamFromOutputStream<String>(){
 @Override
 public String produce(final OutputStream dataSink) throws Exception {
   /*
    * call your application function who produces the data here
    * WARNING: we're in another thread here, so this method shouldn't 
    * write any class field or make assumptions on the state of the outer class. 
    */
   return produceMydata(dataSink)
 }
};

Также есть хорошее введение, в котором объясняются все другие способы преобразования OutputStream в InputStream. Стоит взглянуть.

Габ
источник
1
Учебное пособие по использованию их класса доступно по адресу code.google.com/p/io-tools/wiki/Tutorial_EasyStream
koppor
9

Простое решение, позволяющее избежать копирования буфера, - создать специальный ByteArrayOutputStream:

public class CopyStream extends ByteArrayOutputStream {
    public CopyStream(int size) { super(size); }

    /**
     * Get an input stream based on the contents of this output stream.
     * Do not use the output stream after calling this method.
     * @return an {@link InputStream}
     */
    public InputStream toInputStream() {
        return new ByteArrayInputStream(this.buf, 0, this.count);
    }
}

При необходимости запишите в указанный выше выходной поток, затем вызовите, toInputStreamчтобы получить входной поток через базовый буфер. Считайте выходной поток закрытым после этого момента.

Эрон Райт
источник
7

Я думаю, что лучший способ подключить InputStream к OutputStream - через конвейерные потоки, доступные в пакете java.io, как показано ниже :

// 1- Define stream buffer
private static final int PIPE_BUFFER = 2048;

// 2 -Create PipedInputStream with the buffer
public PipedInputStream inPipe = new PipedInputStream(PIPE_BUFFER);

// 3 -Create PipedOutputStream and bound it to the PipedInputStream object
public PipedOutputStream outPipe = new PipedOutputStream(inPipe);

// 4- PipedOutputStream is an OutputStream, So you can write data to it
// in any way suitable to your data. for example:
while (Condition) {
     outPipe.write(mByte);
}

/*Congratulations:D. Step 4 will write data to the PipedOutputStream
which is bound to the PipedInputStream so after filling the buffer
this data is available in the inPipe Object. Start reading it to
clear the buffer to be filled again by the PipedInputStream object.*/

На мой взгляд, у этого кода есть два основных преимущества:

1 - Дополнительного потребления памяти кроме буфера нет.

2 - Вам не нужно обрабатывать очередь данных вручную

Мостафа Абделлатиф
источник
1
Это было бы здорово, но в javadocs говорится, что если вы будете читать и писать в них в одном потоке, вы можете попасть в тупик. Я бы хотел, чтобы они обновили это с помощью NIO!
Nate Glenn
1

Обычно я стараюсь избегать создания отдельного потока из-за увеличения вероятности тупиковой ситуации, увеличения сложности понимания кода и проблем с обработкой исключений.

Вот предлагаемое мной решение: ProducerInputStream, который создает контент фрагментами путем повторных обращений к producerChunk ():

public abstract class ProducerInputStream extends InputStream {

    private ByteArrayInputStream bin = new ByteArrayInputStream(new byte[0]);
    private ByteArrayOutputStream bout = new ByteArrayOutputStream();

    @Override
    public int read() throws IOException {
        int result = bin.read();
        while ((result == -1) && newChunk()) {
            result = bin.read();
        }
        return result;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int result = bin.read(b, off, len);
        while ((result == -1) && newChunk()) {
            result = bin.read(b, off, len);
        }
        return result;
    }

    private boolean newChunk() {
        bout.reset();
        produceChunk(bout);
        bin = new ByteArrayInputStream(bout.toByteArray());
        return (bout.size() > 0);
    }

    public abstract void produceChunk(OutputStream out);

}
отметка
источник