OutputStream에서 InputStream을 생성하는 가장 효율적인 방법


84

이 페이지 : http://blog.ostermiller.org/convert-java-outputstream-inputstream 은 OutputStream에서 InputStream을 만드는 방법을 설명합니다.

new ByteArrayInputStream(out.toByteArray())

다른 대안은 PipedStreams와 번거로운 새 스레드를 사용하는 것입니다.

나는 많은 메가 바이트를 메모리 바이트 배열에서 새로운 것으로 복사하는 아이디어를 좋아하지 않습니다. 이 작업을보다 효율적으로 수행하는 라이브러리가 있습니까?

편집하다:

Laurence Gonsalves의 조언에 따라 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에서 읽으면 오류가 발생합니다.
Denis Tulskiy

@Lawrence : 2 개의 스레드를 사용하는 이유를 이해할 수 없습니다. InputStream에서 읽은 모든 문자가 적시에 OutputStream에 기록되어야한다는 요구 사항이 아니라면 요.
Stephen C

8
Stephen : 쓰여질 때까지 읽을 수 없습니다. 따라서 스레드가 하나만 있으면 먼저 모든 것을 작성해야하거나 (Vagif가 피하고 싶어하는 큰 메모리 내 배열을 생성) 또는 독자가 입력 대기를 차단하지 않도록 매우 조심해야합니다. , 작성자도 실행하지 않습니다).
Laurence Gonsalves

1
이 제안은 컨테이너가 자신의 스레드를 많이 실행하는 JEE 환경에서 사용하기에 안전한가요?
Toskan 2012

2
@Toskan new Thread이 어떤 이유로 든 컨테이너에 적절하지 않은 경우 사용할 수있는 스레드 풀이 있는지 확인합니다.
Laurence Gonsalves 2012

14

파이프와 스레드를 투명한 방식으로 처리하는 EasyStream 이라는 또 다른 오픈 소스 라이브러리 가 있습니다. 모든 것이 잘되면 그렇게 복잡하지 않습니다. (로렌스 곤살 베스 예를 보면) 문제가 발생합니다.

class1.putDataOnOutputStream (out);

예외가 발생합니다. 이 예제에서 스레드는 단순히 완료되고 예외는 손실되지만 외부 InputStream는 잘릴 수 있습니다.

Easystream은 예외 전파 및 제가 약 1 년 동안 디버깅 해 온 기타 불쾌한 문제를 다룹니다. (저는 라이브러리 관리자입니다. 분명히 제 솔루션이 가장 좋은 솔루션입니다.)) 다음은 사용 방법에 대한 예입니다.

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

}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.