InputStream을 복제하는 방법?


162

처리를 수행하기 위해 메소드에 전달하는 InputStream이 있습니다. 다른 메소드에서 동일한 InputStream을 사용하지만 첫 번째 처리 후 InputStream이 메소드 내부에서 닫혀있는 것으로 보입니다.

InputStream을 복제하여 그를 닫는 메소드로 보내려면 어떻게해야합니까? 다른 해결책이 있습니까?

편집 : InputStream을 닫는 메소드는 lib의 외부 메소드입니다. 닫는 것에 대한 통제권이 없습니다.

private String getContent(HttpURLConnection con) {
    InputStream content = null;
    String charset = "";
    try {
        content = con.getInputStream();
        CloseShieldInputStream csContent = new CloseShieldInputStream(content);
        charset = getCharset(csContent);            
        return  IOUtils.toString(content,charset);
    } catch (Exception e) {
        System.out.println("Error downloading page: " + e);
        return null;
    }
}

private String getCharset(InputStream content) {
    try {
        Source parser = new Source(content);
        return parser.getEncoding();
    } catch (Exception e) {
        System.out.println("Error determining charset: " + e);
        return "UTF-8";
    }
}

2
메소드가 리턴 된 후 스트림을 "재설정"하시겠습니까? 즉, 처음부터 스트림을 읽습니까?
aioobe

예, InputStream을 닫는 메소드는 인코딩 된 문자 세트를 리턴합니다. 두 번째 방법은 첫 번째 방법에서 찾은 문자 집합을 사용하여 InputStream을 문자열로 변환하는 것입니다.
Renato Dinhani

이 경우 내 답변에 설명 된 내용을 수행 할 수 있어야합니다.
Kaj

나는 그것을 해결하는 가장 좋은 방법을 모르지만, 그렇지 않으면 내 문제를 해결합니다. Jericho HTML 구문 분석기의 toString 메소드는 올바른 형식으로 형식화 된 문자열을 리턴합니다. 내가 지금 필요한 전부입니다.
Renato Dinhani

답변:


188

당신이 한 번 이상 동일한 정보를 읽어 싶지 모든, 입력 데이터가 메모리에 맞게 충분히 작은 경우, 당신은 당신의 데이터를 복사 할 수 있습니다 InputStreamA를 있는 ByteArrayOutputStream .

그런 다음 연관된 바이트 배열을 확보하고 원하는만큼 "복제 된" ByteArrayInputStream을 열 수 있습니다.

ByteArrayOutputStream baos = new ByteArrayOutputStream();

// Fake code simulating the copy
// You can generally do better with nio if you need...
// And please, unlike me, do something about the Exceptions :D
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) > -1 ) {
    baos.write(buffer, 0, len);
}
baos.flush();

// Open new InputStreams using the recorded bytes
// Can be repeated as many times as you wish
InputStream is1 = new ByteArrayInputStream(baos.toByteArray()); 
InputStream is2 = new ByteArrayInputStream(baos.toByteArray()); 

그러나 새로운 데이터를 수신하기 위해 원본 스트림을 열어 두어야하는 경우이 외부를 추적해야합니다. close() 방법 어떻게 든 호출되지 않도록해야합니다.

업데이트 (2019) :

Java 9부터 중간 비트를 다음으로 바꿀 수 있습니다 InputStream.transferTo.

ByteArrayOutputStream baos = new ByteArrayOutputStream();
input.transferTo(baos);
InputStream firstClone = new ByteArrayInputStream(baos.toByteArray()); 
InputStream secondClone = new ByteArrayInputStream(baos.toByteArray()); 

InputStream 복사와 관련이없는 내 문제에 대한 또 다른 해결책을 모색하지만 InputStream을 복사 해야하는 경우 이것이 최선의 해결책이라고 생각합니다.
Renato Dinhani

7
이 방법은 입력 스트림의 전체 내용에 비례하여 메모리를 소비합니다. 여기TeeInputStream 답변에 설명 된대로 사용 하는 것이 좋습니다 .
aioobe

2
아파치 커먼즈의 IOUtils에는 코드 중간에 버퍼 읽기 / 쓰기를 수행하는 복사 방법이 있습니다.
rethab

31

Apache를 사용하려고합니다 CloseShieldInputStream.

스트림이 닫히는 것을 방지하는 래퍼입니다. 당신은 이런 식으로 할 것입니다.

InputStream is = null;

is = getStream(); //obtain the stream 
CloseShieldInputStream csis = new CloseShieldInputStream(is);

// call the bad function that does things it shouldn't
badFunction(csis);

// happiness follows: do something with the original input stream
is.read();

좋아 보이지만 여기서 작동하지 않습니다. 코드로 게시물을 편집하겠습니다.
Renato Dinhani

CloseShield원래 HttpURLConnection입력 스트림이 어딘가에 닫혀 있기 때문에 작동하지 않습니다 . 메소드가 보호 된 스트림으로 IOUtils를 호출해서는 안 IOUtils.toString(csContent,charset)됩니까?
Anthony Accioly

어쩌면 이것 일 수 있습니다. HttpURLConnection이 닫히는 것을 막을 수 있습니까?
Renato Dinhani

1
@ 레나토. 어쩌면 문제는 close()전화 가 아니라 스트림이 끝까지 읽히고 있다는 사실입니다. 이후 mark()reset()HTTP 연결을위한 최선의 방법을하지 않을 수 있습니다, 어쩌면 당신은 내 대답에 설명 된 바이트 배열 방식을 살펴해야한다.
Anthony Accioly

1
한 가지 더, 항상 동일한 URL에 대한 새 연결을 열 수 있습니다. 여기를보십시오 : stackoverflow.com/questions/5807340/…
Anthony Accioly

11

복제 할 수 없으며 문제를 해결하는 방법은 데이터 소스에 따라 다릅니다.

한 가지 해결책은 InputStream의 모든 데이터를 바이트 배열로 읽은 다음 해당 바이트 배열 주위에 ByteArrayInputStream을 작성하고 해당 입력 스트림을 메소드에 전달하는 것입니다.

편집 1 : 즉 다른 방법이 동일한 데이터를 읽어야하는 경우입니다. 즉, 스트림을 "재설정"하고 싶습니다.


도움이 필요한 부분을 모르겠습니다. 스트림에서 읽는 방법을 알고 있습니까? InputStream에서 모든 데이터를 읽고 ByteArrayOutputStream에 데이터를 씁니다. 모든 데이터 읽기를 완료 한 후 ByteArrayOutputStream에서 toByteArray ()를 호출하십시오. 그런 다음 해당 바이트 배열을 ByteArrayInputStream의 생성자로 전달하십시오.
Kaj

8

스트림에서 읽은 데이터가 큰 경우 Apache Commons IO의 TeeInputStream을 사용하는 것이 좋습니다. 그렇게하면 본질적으로 입력을 복제하고 t'd 파이프를 복제본으로 전달할 수 있습니다.


5

이것은 모든 상황에서 작동하지는 않지만 여기에 내가 한 일이 있습니다. FilterInputStream 클래스를 확장 하고 외부 lib가 데이터를 읽을 때 필요한 바이트 처리를 수행합니다.

public class StreamBytesWithExtraProcessingInputStream extends FilterInputStream {

    protected StreamBytesWithExtraProcessingInputStream(InputStream in) {
        super(in);
    }

    @Override
    public int read() throws IOException {
        int readByte = super.read();
        processByte(readByte);
        return readByte;
    }

    @Override
    public int read(byte[] buffer, int offset, int count) throws IOException {
        int readBytes = super.read(buffer, offset, count);
        processBytes(buffer, offset, readBytes);
        return readBytes;
    }

    private void processBytes(byte[] buffer, int offset, int readBytes) {
       for (int i = 0; i < readBytes; i++) {
           processByte(buffer[i + offset]);
       }
    }

    private void processByte(int readByte) {
       // TODO do processing here
    }

}

그런 다음 StreamBytesWithExtraProcessingInputStream입력 스트림에서 전달한 인스턴스를 전달하면 됩니다. 원래 입력 스트림을 생성자 매개 변수로 사용합니다.

이것은 바이트 단위로 작동하므로 고성능이 필요한 경우 이것을 사용하지 마십시오.


3

UPD. 주석을 확인하십시오. 정확히 무엇을 요구했는지는 아닙니다.

사용중인 apache.commons경우을 사용하여 스트림을 복사 할 수 있습니다 IOUtils.

다음 코드를 사용할 수 있습니다.

InputStream = IOUtils.toBufferedInputStream(toCopy);

상황에 적합한 전체 예는 다음과 같습니다.

public void cloneStream() throws IOException{
    InputStream toCopy=IOUtils.toInputStream("aaa");
    InputStream dest= null;
    dest=IOUtils.toBufferedInputStream(toCopy);
    toCopy.close();
    String result = new String(IOUtils.toByteArray(dest));
    System.out.println(result);
}

이 코드에는 몇 가지 종속성이 필요합니다.

메이븐

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

그래들

'commons-io:commons-io:2.4'

이 방법에 대한 DOC 참조는 다음과 같습니다.

InputStream의 전체 내용을 가져오고 결과 InputStream과 동일한 데이터를 나타냅니다. 이 방법은 다음과 같은 경우에 유용합니다.

소스 입력 스트림이 느립니다. 네트워크 리소스가 연결되어 있으므로 오랫동안 열어 둘 수 없습니다. 네트워크 시간 초과가 연결되어 있습니다.

http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/IOUtils.html#toBufferedInputStream(java.io.InputStream)에 대한 자세한 내용은 IOUtils여기를 참조 하십시오.


7
입력 스트림을 복제 하지 않고 버퍼링 만합니다. 그것은 동일하지 않습니다. OP는 동일한 스트림을 다시 읽고 싶어합니다.
Raphael

1

다음은 Kotlin의 솔루션입니다.

InputStream을 ByteArray에 복사 할 수 있습니다

val inputStream = ...

val byteOutputStream = ByteArrayOutputStream()
inputStream.use { input ->
    byteOutputStream.use { output ->
        input.copyTo(output)
    }
}

val byteInputStream = ByteArrayInputStream(byteOutputStream.toByteArray())

byteInputStream여러 번 읽어야하는 경우 byteInputStream.reset()다시 읽기 전에 전화 하십시오.

https://code.luasoftware.com/tutorials/kotlin/how-to-clone-inputstream/


0

아래 클래스는 트릭을 수행해야합니다. 인스턴스를 생성하고 "multiply"메소드를 호출 한 다음 소스 입력 스트림과 필요한 복제 량을 제공하십시오.

중요 : 모든 복제 된 스트림을 별도의 스레드에서 동시에 소비해야합니다.

package foo.bar;

import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class InputStreamMultiplier {
    protected static final int BUFFER_SIZE = 1024;
    private ExecutorService executorService = Executors.newCachedThreadPool();

    public InputStream[] multiply(final InputStream source, int count) throws IOException {
        PipedInputStream[] ins = new PipedInputStream[count];
        final PipedOutputStream[] outs = new PipedOutputStream[count];

        for (int i = 0; i < count; i++)
        {
            ins[i] = new PipedInputStream();
            outs[i] = new PipedOutputStream(ins[i]);
        }

        executorService.execute(new Runnable() {
            public void run() {
                try {
                    copy(source, outs);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

        return ins;
    }

    protected void copy(final InputStream source, final PipedOutputStream[] outs) throws IOException {
        byte[] buffer = new byte[BUFFER_SIZE];
        int n = 0;
        try {
            while (-1 != (n = source.read(buffer))) {
                //write each chunk to all output streams
                for (PipedOutputStream out : outs) {
                    out.write(buffer, 0, n);
                }
            }
        } finally {
            //close all output streams
            for (PipedOutputStream out : outs) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

질문에 대답하지 않습니다. 그는 한 가지 방법으로 스트림을 사용하여 문자 집합을 확인한 다음 두 번째 방법으로 해당 문자 집합과 함께 다시 읽습니다.
Lorne의 후작

0

입력 스트림을 복제하는 것은 좋은 생각이 아닐 수 있습니다. 복제하려면 입력 스트림의 세부 사항에 대한 깊은 지식이 필요하기 때문입니다. 이에 대한 해결 방법은 동일한 소스에서 다시 읽는 새 입력 스트림을 작성하는 것입니다.

따라서 일부 Java 8 기능을 사용하면 다음과 같습니다.

public class Foo {

    private Supplier<InputStream> inputStreamSupplier;

    public void bar() {
        procesDataThisWay(inputStreamSupplier.get());
        procesDataTheOtherWay(inputStreamSupplier.get());
    }

    private void procesDataThisWay(InputStream) {
        // ...
    }

    private void procesDataTheOtherWay(InputStream) {
        // ...
    }
}

이 방법은 이미 존재하는 코드를 재사용한다는 긍정적 인 효과가 있습니다. inputStreamSupplier . 합니다. 또한 스트림 복제를 위해 두 번째 코드 경로를 유지할 필요가 없습니다.

반면에, 스트림에서 읽기가 비싸면 (연결이 낮은 밴드에서 이루어지기 때문에)이 방법은 비용을 두 배로 늘립니다. 스트림 컨텐츠를 먼저 로컬에 저장하고 InputStream해당 로컬 자원을 제공하는 특정 공급 업체를 사용하여이를 피할 수 있습니다 .


이 대답은 명확하지 않습니다. 기존 공급자를 어떻게 공급 업체에 초기화 is합니까?
user1156544

@ user1156544 필자 가 입력 스트림을 복제하는 것은 좋은 생각이 아닐 수 있습니다. 복제되는 입력 스트림의 세부 사항에 대한 깊은 지식이 필요하기 때문입니다. 공급 업체를 사용하여 기존 스트림을 입력 스트림으로 만들 수 없습니다. 공급자는 사용할 수 java.io.File또는 java.net.URL예를 들어 새로운 입력이 호출 될 때마다 스트리밍 만들 수 있습니다.
SpaceTrucker

나는 지금 본다. OP가 명시 적으로 요청하는 것처럼 입력 스트림에서는 작동하지 않지만 파일 또는 URL이 원래 데이터 소스 인 경우에는 파일 또는 URL에서 작동합니다. 감사합니다
user1156544
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.