Read stream twice
如何读取相同的inputstream两次?是否可以复制?
我需要从Web获取一个图像,在本地保存它,然后返回保存的图像。我只是想使用同一个流会更快,而不是对下载的内容启动一个新的流,然后再阅读它。
您可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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); } |
根据输入流的来源,您可能无法重置它。您可以使用
如果是,可以在输入流上调用
如果你的
1 2 3 4 5 | InputStream bufferdInputStream = new BufferedInputStream(yourInputStream); bufferdInputStream.mark(some_value); //read your bufferdInputStream bufferdInputStream.reset(); //read it again |
可以用pushbackinputstream包装输入流。pushbackinputstream允许读取已读取的未读("写回")字节,因此可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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将原始输入流存储为委托。这允许创建新的类定义,允许自动"未读"原始数据。此类的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | public class TryReadInputStream extends FilterInputStream { private final int maxPushbackBufferSize; /** * Creates a <wyn>FilterInputStream</wyn> * by assigning the argument <wyn>in</wyn> * to the field <wyn>this.in</wyn> so as * to remember it for later use. * * @param in the underlying input stream, or <wyn>null</wyn> 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 <wyn>length</wyn> 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 <wyn>buffer</wyn> * @aram length how many bytes to read from the stream to buff. Length needs to be less than * <wyn>maxPushbackBufferSize</wyn> 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); } } } |
这个类有两个方法。一个用于读取现有缓冲区(定义类似于调用inputstream类的
现在让我们来看看我们班的实际情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 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转换为字节数组,然后根据需要在数组中迭代多次。在这个转换后的输入流中,您可以使用第三方LIBS或JAVA中的多个解决方案找到字节数组中的几个解决方案。注意,如果读取内容太大,可能会遇到一些内存问题。
最后,如果您需要读取图像,请使用:
1 |
使用
怎么样:
1 2 3 4 5 6 7 8 9 10 | 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' } |
将inputstream转换为字节,然后将其传递给savefile函数,在该函数中,您将其组装为inputstream。同样在原始函数中,使用字节来执行其他任务
将
在一个单独的线程中,读取整个源输入流,并隐式地将输入数据传输到目标输入流。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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); } |
注意消耗完后关闭输入流,关闭运行的线程:
在这种情况下,您需要将其拆分为多个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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(); } } } |
如果有人正在运行一个Spring引导应用程序,而你想要读一个
首先,您需要使用Spring的
1 |
但这不是全部。您还需要使用一个可以为您缓冲流的请求工厂,例如:
1 2 | ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()); RestTemplate restTemplate = new RestTemplate(factory); |
或者,如果您正在使用工厂bean,那么(这是Kotlin,但是仍然是):
1 2 3 4 5 6 | @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/