Найефективніший спосіб створити InputStream з OutputStream


84

Ця сторінка: 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))

Відповіді:


72

Якщо ви не хочете копіювати всі дані одночасно в буфер в пам’яті, вам доведеться мати свій код, який використовує 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 з іншого потоку, це видасть вам помилку.
Денис Тульський

@Lawrence: Я не розумію вашої аргументації використання 2-х потоків ... ЩО БЕЗ вимоги, щоб усі символи, прочитані з InputStream, своєчасно записувались у OutputStream.
Стівен С,

8
Стівен: ти не можеш щось прочитати, поки це не написано. Отже, лише з одним потоком потрібно або написати все спочатку (створивши великий масив пам’яті, якого Вагіф хотів уникнути), або потрібно, щоб їх чергували, дуже обережно, щоб читач ніколи не блокував очікування введення (бо якщо він це зробить) , письменник ніколи не дійде до страти).
Лоуренс Гонсалвес

1
чи безпечно цю пропозицію використовувати в середовищі JEE, де на контейнері, ймовірно, працює багато власних потоків?
Тоскан

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, який створює вміст у шматках шляхом багаторазових викликів produceChunk ():

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);

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.