如何两次读取相同的输入流?有可能以某种方式复制它吗?
我需要从网上获取图片,在本地保存,然后返回保存的图片。我只是认为使用相同的流而不是为下载的内容启动新流然后再次阅读它会更快。
答案 0 :(得分:90)
您可以使用org.apache.commons.io.IOUtils.copy
将InputStream的内容复制到字节数组,然后使用ByteArrayInputStream重复读取字节数组。 E.g:
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 :(得分:24)
根据InputStream的来源,您可能无法重置它。您可以使用mark()
检查是否支持reset()
和markSupported()
。
如果是,您可以在InputStream上调用reset()
返回到开头。如果没有,您需要再次从源读取InputStream。
答案 2 :(得分:8)
如果您的InputStream
支持使用标记,那么您可以mark()
输入流,然后reset()
。如果您的InputStrem
不支持标记,那么您可以使用课程java.io.BufferedInputStream
,这样您就可以将您的信息流嵌入BufferedInputStream
这样的
InputStream bufferdInputStream = new BufferedInputStream(yourInputStream);
bufferdInputStream.mark(some_value);
//read your bufferdInputStream
bufferdInputStream.reset();
//read it again
答案 3 :(得分:7)
您可以使用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存储字节的内部缓冲区,因此它确实在内存中创建了一个缓冲区,其中包含字节&#34;写回&#34;。
了解这种方法后,我们可以进一步将其与FilterInputStream结合使用。 FilterInputStream将原始输入流存储为委托。这允许创建新的类定义,允许&#34; 未读&#34;原始数据自动。这个类的定义如下:
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);
}
}
}
这个类有两种方法。一个用于读入现有缓冲区(定义类似于调用InputStream类的public int read(byte b[], int off, int len)
)。第二个返回新缓冲区(如果要读取的缓冲区大小未知,则可能更有效)。
现在让我们看看我们的课程:
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
}
}
答案 4 :(得分:4)
如果您使用的是InputStream
的实现,则可以查看InputStream#markSupported()
的结果,告诉您是否可以使用mark()
/ reset()
方法。
如果您在阅读时可以标记流,请致电reset()
返回开始。
如果你不能,你将不得不再次打开一个流。
另一个解决方案是将InputStream转换为字节数组,然后根据需要迭代数组。您可以使用第三方库来查找此帖Convert InputStream to byte array in Java中的多个解决方案。注意,如果读取的内容太大,您可能会遇到一些内存问题。
最后,如果您需要阅读图像,请使用:
BufferedImage image = ImageIO.read(new URL("http://www.example.com/images/toto.jpg"));
使用ImageIO#read(java.net.URL)
也可以使用缓存。
答案 5 :(得分:2)
将inputstream转换为字节,然后将其传递给savefile函数,然后将其组合到inputstream中。 同样在原始函数中使用字节来用于其他任务
答案 6 :(得分:2)
怎么样:
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'
}
答案 7 :(得分:0)
如果任何人都在Spring Boot应用程序中运行,并且您想读取RestTemplate
的响应正文(这就是为什么我想两次读取流的原因),则有一种更简洁的方法这样。
首先,您需要使用Spring的StreamUtils
将流复制到String:
String text = StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()))
但这还不是全部。您还需要使用可以为您缓冲流的请求工厂,如下所示:
ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate(factory);
或者,如果您使用的是工厂bean,那么(尽管如此,这是Kotlin):
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
fun createRestTemplate(): RestTemplate = RestTemplateBuilder()
.requestFactory { BufferingClientHttpRequestFactory(SimpleClientHttpRequestFactory()) }
.additionalInterceptors(loggingInterceptor)
.build()
答案 8 :(得分:0)
要将InputStream
分成两部分,同时避免将所有数据加载到内存中,然后独立处理它们:
OutputStream
,恰好是:PipedOutputStream
PipedInputStream
是返回的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()
以防万一,您需要将其拆分为多个InputStream
,而不是两个。在代码的上一片段中,为您自己的实现替换类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();
}
}
}
答案 9 :(得分:0)
如果您使用RestTemplate进行http调用,只需添加一个拦截器即可。 响应正文由ClientHttpResponse的实现缓存。 现在,输入流可以根据需要从Respose中检索多次
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);
答案 10 :(得分:0)
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.