입력 스트림을 두 번 읽습니다.
같은 입력 스트림을 어떻게 두 번 읽습니까?어떻게 해서든 복사할 수 있을까요?
웹에서 이미지를 가져와 로컬에 저장한 다음 저장된 이미지를 반환해야 합니다.다운로드 받은 콘텐츠에 대해 새로운 스트림을 시작하고 다시 읽는 것보다 같은 스트림을 사용하는 것이 더 빠를 것 같아서요.
를 사용하여 InputStream의 내용을 바이트 배열에 복사한 후 ByteArrayInputStream을 사용하여 바이트 배열에서 반복적으로 읽을 수 있습니다.예:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
org.apache.commons.io.IOUtils.copy(in, baos);
byte[] bytes = baos.toByteArray();
// either
while (needToReadAgain) {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
yourReadMethodHere(bais);
}
// or
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
while (needToReadAgain) {
bais.reset();
yourReadMethodHere(bais);
}
InputStream, InputStream, InputStream, InputStream. 하면 '아까보다'가 할 수 있어요.mark()
★★★★★★★★★★★★★★★★★」reset()
는 을 사용하여 됩니다.markSupported()
.
번호로 할 수 .reset()
[ Input Stream ]를 선택합니다.InputStream을 사용합니다.
InputStream
마크 사용으로 서포트할 수 있습니다.mark()
, 으로 Stream, your 、 [ Stream ]reset()
your your your InputStrem
를 사용할 수 .java.io.BufferedInputStream
스트림을 내장할 수 있습니다.BufferedInputStream
InputStream bufferdInputStream = new BufferedInputStream(yourInputStream);
bufferdInputStream.mark(some_value);
//read your bufferdInputStream
bufferdInputStream.reset();
//read it again
입력 스트림을 PushbackInputStream으로 랩할 수 있습니다.PushbackInputStream을 사용하면 이미 읽은 바이트를 읽지 않고("쓰기") 다음과 같이 수행할 수 있습니다.
public class StreamTest {
public static void main(String[] args) throws IOException {
byte[] bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
InputStream originalStream = new ByteArrayInputStream(bytes);
byte[] readBytes = getBytes(originalStream, 3);
printBytes(readBytes); // prints: 1 2 3
readBytes = getBytes(originalStream, 3);
printBytes(readBytes); // prints: 4 5 6
// now let's wrap it with PushBackInputStream
originalStream = new ByteArrayInputStream(bytes);
InputStream wrappedStream = new PushbackInputStream(originalStream, 10); // 10 means that maximnum 10 characters can be "written back" to the stream
readBytes = getBytes(wrappedStream, 3);
printBytes(readBytes); // prints 1 2 3
((PushbackInputStream) wrappedStream).unread(readBytes, 0, readBytes.length);
readBytes = getBytes(wrappedStream, 3);
printBytes(readBytes); // prints 1 2 3
}
private static byte[] getBytes(InputStream is, int howManyBytes) throws IOException {
System.out.print("Reading stream: ");
byte[] buf = new byte[howManyBytes];
int next = 0;
for (int i = 0; i < howManyBytes; i++) {
next = is.read();
if (next > 0) {
buf[i] = (byte) next;
}
}
return buf;
}
private static void printBytes(byte[] buffer) throws IOException {
System.out.print("Reading stream: ");
for (int i = 0; i < buffer.length; i++) {
System.out.print(buffer[i] + " ");
}
System.out.println();
}
}
PushbackInputStream은 바이트의 내부 버퍼를 저장하므로 실제로 메모리에 "쓰기된" 바이트를 저장하는 버퍼를 생성합니다.
이 방법을 알면 FilterInputStream과 조합할 수 있습니다.FilterInputStream은 원래 입력 스트림을 위임자로 저장합니다.그러면 원본 데이터를 자동으로 "읽지 않음"할 수 있는 새 클래스 정의를 만들 수 있습니다.이 클래스의 정의는 다음과 같습니다.
public class TryReadInputStream extends FilterInputStream {
private final int maxPushbackBufferSize;
/**
* Creates a <code>FilterInputStream</code>
* by assigning the argument <code>in</code>
* to the field <code>this.in</code> so as
* to remember it for later use.
*
* @param in the underlying input stream, or <code>null</code> if
* this instance is to be created without an underlying stream.
*/
public TryReadInputStream(InputStream in, int maxPushbackBufferSize) {
super(new PushbackInputStream(in, maxPushbackBufferSize));
this.maxPushbackBufferSize = maxPushbackBufferSize;
}
/**
* Reads from input stream the <code>length</code> of bytes to given buffer. The read bytes are still avilable
* in the stream
*
* @param buffer the destination buffer to which read the data
* @param offset the start offset in the destination <code>buffer</code>
* @aram length how many bytes to read from the stream to buff. Length needs to be less than
* <code>maxPushbackBufferSize</code> or IOException will be thrown
*
* @return number of bytes read
* @throws java.io.IOException in case length is
*/
public int tryRead(byte[] buffer, int offset, int length) throws IOException {
validateMaxLength(length);
// NOTE: below reading byte by byte instead of "int bytesRead = is.read(firstBytes, 0, maxBytesOfResponseToLog);"
// because read() guarantees to read a byte
int bytesRead = 0;
int nextByte = 0;
for (int i = 0; (i < length) && (nextByte >= 0); i++) {
nextByte = read();
if (nextByte >= 0) {
buffer[offset + bytesRead++] = (byte) nextByte;
}
}
if (bytesRead > 0) {
((PushbackInputStream) in).unread(buffer, offset, bytesRead);
}
return bytesRead;
}
public byte[] tryRead(int maxBytesToRead) throws IOException {
validateMaxLength(maxBytesToRead);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); // as ByteArrayOutputStream to dynamically allocate internal bytes array instead of allocating possibly large buffer (if maxBytesToRead is large)
// NOTE: below reading byte by byte instead of "int bytesRead = is.read(firstBytes, 0, maxBytesOfResponseToLog);"
// because read() guarantees to read a byte
int nextByte = 0;
for (int i = 0; (i < maxBytesToRead) && (nextByte >= 0); i++) {
nextByte = read();
if (nextByte >= 0) {
baos.write((byte) nextByte);
}
}
byte[] buffer = baos.toByteArray();
if (buffer.length > 0) {
((PushbackInputStream) in).unread(buffer, 0, buffer.length);
}
return buffer;
}
private void validateMaxLength(int length) throws IOException {
if (length > maxPushbackBufferSize) {
throw new IOException(
"Trying to read more bytes than maxBytesToRead. Max bytes: " + maxPushbackBufferSize + ". Trying to read: " +
length);
}
}
}
이 클래스에는 두 가지 메서드가 있습니다.기존 위한 입니다(는 ""를 호출하는 합니다).public int read(byte b[], int off, int len)
( InputStream " " " " " )하는 두 알 수 더일 수 ).두 번째로 새 버퍼를 반환합니다(읽을 버퍼 크기를 알 수 없는 경우 더 효과적일 수 있습니다).
이제 우리 반의 활동을 살펴보겠습니다.
public class StreamTest2 {
public static void main(String[] args) throws IOException {
byte[] bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
InputStream originalStream = new ByteArrayInputStream(bytes);
byte[] readBytes = getBytes(originalStream, 3);
printBytes(readBytes); // prints: 1 2 3
readBytes = getBytes(originalStream, 3);
printBytes(readBytes); // prints: 4 5 6
// now let's use our TryReadInputStream
originalStream = new ByteArrayInputStream(bytes);
InputStream wrappedStream = new TryReadInputStream(originalStream, 10);
readBytes = ((TryReadInputStream) wrappedStream).tryRead(3); // NOTE: no manual call to "unread"(!) because TryReadInputStream handles this internally
printBytes(readBytes); // prints 1 2 3
readBytes = ((TryReadInputStream) wrappedStream).tryRead(3);
printBytes(readBytes); // prints 1 2 3
readBytes = ((TryReadInputStream) wrappedStream).tryRead(3);
printBytes(readBytes); // prints 1 2 3
// we can also call normal read which will actually read the bytes without "writing them back"
readBytes = getBytes(wrappedStream, 3);
printBytes(readBytes); // prints 1 2 3
readBytes = getBytes(wrappedStream, 3);
printBytes(readBytes); // prints 4 5 6
readBytes = ((TryReadInputStream) wrappedStream).tryRead(3); // now we can try read next bytes
printBytes(readBytes); // prints 7 8 9
readBytes = ((TryReadInputStream) wrappedStream).tryRead(3);
printBytes(readBytes); // prints 7 8 9
}
}
「」를 .InputStream
두 개로 나누어 메모리에 모든 데이터를 로드하지 않고 독립적으로 처리합니다.
- 「 」를 합니다.
OutputStream
는 , , , , , , , , , , , , , , , , , .PipedOutputStream
- 합니다.이것들은 "PipedOutputStream" 입니다.
PipedInputStream
된 「」입니다.InputStream
. - 방금 작성한 소싱 InputStream 연결
OutputStream
모든 것이 소싱에서 읽습니다.InputStream
는, 양쪽 모두로 기술됩니다.OutputStream
이미 구현되어 있기 때문에 구현할 필요가 없습니다.TeeInputStream
(commons.io). 분리된 스레드 내에서 소싱 inputStream 전체를 읽고 암묵적으로 입력 데이터가 대상 inputStreams로 전송됩니다.
public static final List<InputStream> splitInputStream(InputStream input) throws IOException { Objects.requireNonNull(input); PipedOutputStream pipedOut01 = new PipedOutputStream(); PipedOutputStream pipedOut02 = new PipedOutputStream(); List<InputStream> inputStreamList = new ArrayList<>(); inputStreamList.add(new PipedInputStream(pipedOut01)); inputStreamList.add(new PipedInputStream(pipedOut02)); TeeOutputStream tout = new TeeOutputStream(pipedOut01, pipedOut02); TeeInputStream tin = new TeeInputStream(input, tout, true); Executors.newSingleThreadExecutor().submit(tin::readAllBytes); return Collections.unmodifiableList(inputStreamList); }
사용 후 inputStreams를 닫고 다음 작업을 수행하는 스레드를 닫으십시오.TeeInputStream.readAllBytes()
경우에 따라서는 2개가 아닌 여러 개로 분할해야 합니다.이전 코드 조각의 클래스를 바꿉니다.TeeOutputStream
사용자 자신의 구현에 사용할 수 있습니다.List<OutputStream>
를 덮어씁니다.OutputStream
인터페이스:
public final class TeeListOutputStream extends OutputStream {
private final List<? extends OutputStream> branchList;
public TeeListOutputStream(final List<? extends OutputStream> branchList) {
Objects.requireNonNull(branchList);
this.branchList = branchList;
}
@Override
public synchronized void write(final int b) throws IOException {
for (OutputStream branch : branchList) {
branch.write(b);
}
}
@Override
public void flush() throws IOException {
for (OutputStream branch : branchList) {
branch.flush();
}
}
@Override
public void close() throws IOException {
for (OutputStream branch : branchList) {
branch.close();
}
}
}
의 실장을 사용하고 있는 경우는, 그 결과를 체크하고, / 메서드를 사용할 수 있는지 아닌지를 확인할 수 있습니다.
읽을 때 스트림을 표시할 수 있다면reset()
다시 시작할 수 있습니다.
그렇게 할 수 없다면 개울을 다시 열어야 할 것이다.
다른 솔루션은 InputStream을 바이트 배열로 변환한 후 어레이에서 필요한 시간만큼 반복하는 것입니다.이 포스트에서는 서드파티 libs를 사용하여 InputStream을 바이트 배열로 변환합니다.주의: 읽기 내용이 너무 크면 메모리 문제가 발생할 수 있습니다.
마지막으로 이미지를 읽을 필요가 있는 경우 다음을 사용합니다.
BufferedImage image = ImageIO.read(new URL("http://www.example.com/images/toto.jpg"));
를 사용하면 캐시를 사용할 수도 있습니다.
그럼 어떻게 해?
if (stream.markSupported() == false) {
// lets replace the stream object
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IOUtils.copy(stream, baos);
stream.close();
stream = new ByteArrayInputStream(baos.toByteArray());
// now the stream should support 'mark' and 'reset'
}
입력 스트림을 바이트로 변환한 후 이를 savefile 함수로 전달합니다.이 함수는 입력 스트림에 이를 결합합니다.또한 원래 기능에서는 바이트를 사용하여 다른 작업에 사용
Spring Boot 앱에서 실행 중인 사용자가 있을 때 응답 본문을 읽고 싶은 경우RestTemplate
(그래서 스트림을 두 번 읽고 싶다) 깔끔한 방법이 있습니다.
일단 봄의 것을 써야 합니다.StreamUtils
스트림을 문자열에 복사하려면:
String text = StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()))
하지만 그게 다가 아닙니다.또한 다음과 같이 스트림을 버퍼링할 수 있는 요청 팩토리를 사용해야 합니다.
ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate(factory);
또는 공장용 콩을 사용하고 있다면 (이것은 Kotlin이지만)
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
fun createRestTemplate(): RestTemplate = RestTemplateBuilder()
.requestFactory { BufferingClientHttpRequestFactory(SimpleClientHttpRequestFactory()) }
.additionalInterceptors(loggingInterceptor)
.build()
출처 : https://objectpartners.com/2018/03/01/log-your-resttemplate-request-and-response-without-destroying-the-body/
RestTemplate를 사용하여 http 콜을 발신하는 경우 단순히 인터셉터를 추가합니다.응답 본문은 ClientHttpResponse 구현에 의해 캐시됩니다.이제 필요한 횟수만큼 응답에서 입력 스트림을 검색할 수 있습니다.
ClientHttpRequestInterceptor interceptor = new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
ClientHttpResponse response = execution.execute(request, body);
// additional work before returning response
return response
}
};
// Add the interceptor to RestTemplate Instance
restTemplate.getInterceptors().add(interceptor);
ByteArrayInputStream ins = new ByteArrayInputStream("Hello".getBytes());
System.out.println("ins.available() at begining:: " + ins.available());
ins.mark(0);
// Read input stream for some operations
System.out.println("ins.available() after reading :: " + ins.available());
ins.reset();
System.out.println("ins.available() after resetting :: " + ins.available());
// ins is ready for reading once again.
언급URL : https://stackoverflow.com/questions/9501237/read-input-stream-twice
'programing' 카테고리의 다른 글
VueStoreFront: 커스텀 모듈: 상태는 갱신되지만 컴포넌트는 갱신되지 않음 (0) | 2022.07.11 |
---|---|
롬복(lombok)을 사용하여 기존 객체에서 객체 만들기 (0) | 2022.07.11 |
console.log()가 브라우저에 아무것도 기록하지 않는 이유는 무엇입니까? (0) | 2022.07.11 |
인스턴스가 생성된 후 Vue 인스턴스에 구성 요소 추가 (0) | 2022.07.11 |
Vue Mixin 계산 함수 통과 매개 변수 (0) | 2022.07.11 |