Java 读取输入流两次
如何读取同一输入流两次?有可能以某种方式复制它吗Java 读取输入流两次,java,inputstream,Java,Inputstream,如何读取同一输入流两次?有可能以某种方式复制它吗 我需要从网络上获取图像,将其保存在本地,然后返回保存的图像。我只是认为使用同一个流会更快,而不是启动下载内容的新流,然后再次阅读。根据输入流的来源,您可能无法重置它。您可以使用markSupported()检查是否支持mark()和reset() 如果是,您可以在InputStream上调用reset(),返回到开头。如果没有,则需要再次从源读取InputStream。如果使用的是的实现,则可以检查该实现的结果,告诉您是否可以使用该方法/ 如果可
我需要从网络上获取图像,将其保存在本地,然后返回保存的图像。我只是认为使用同一个流会更快,而不是启动下载内容的新流,然后再次阅读。根据输入流的来源,您可能无法重置它。您可以使用
markSupported()
检查是否支持mark()
和reset()
如果是,您可以在InputStream上调用
reset()
,返回到开头。如果没有,则需要再次从源读取InputStream。如果使用的是的实现,则可以检查该实现的结果,告诉您是否可以使用该方法/
如果可以在读取时标记流,则调用reset()
返回开始
如果你不能,你将不得不再次打开一条小溪
另一种解决方案是将InputStream转换为字节数组,然后根据需要多次迭代该数组。你可以在这篇文章中找到几种使用第三方LIB或不使用第三方LIB的解决方案。注意,如果读取的内容太大,您可能会遇到一些内存问题
最后,如果您需要读取图像,请使用:
BufferedImage image = ImageIO.read(new URL("http://www.example.com/images/toto.jpg"));
使用还允许您使用缓存。将inputstream转换为字节,然后将其传递给savefile函数,在这里您可以将其组装为inputstream。 另外,在原始函数“使用字节用于其他任务”中,您可以使用将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
支持使用mark,那么您可以mark()
您的InputStream,然后reset()
它。如果您的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();
}
}
这个类有两个方法。一个用于读入现有缓冲区(定义类似于调用InputStream类的public int read(字节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
}
}
那么:
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'
}
如果有人在Spring Boot应用程序中运行,并且您想要读取
RestTemplate
(这就是我想要读取两次流的原因)的响应体,那么有一种干净的(er)方法可以做到这一点
首先,需要使用Spring的StreamUtils
将流复制到字符串:
String text = StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()))
但这还不是全部。您还需要使用一个可以为您缓冲流的请求工厂,如下所示:
ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate(factory);
或者,如果您使用的是工厂bean,那么(这是Kotlin,但无论如何):
来源:用于将
输入流
一分为二,同时避免加载内存中的所有数据,然后独立处理:
OutputStream
,确切地说:PipedOutputStream
PipedInputStream
是返回的InputStream
OutputStream
连接。因此,从源InputStream
读取的所有内容都将同时写入OutputStream
。不需要实现它,因为它已经在TeeInputStream
(commons.io)中完成了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);
}
如果您使用RestTemplate进行http调用,只需添加一个拦截器。 响应体由ClientHttpResponse的实现缓存。 现在,可以根据需要多次从respose检索inputstream
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);
可能使用mark和Reset我认为这是个坏主意,生成的数组可能很大,会占用设备的内存。@Paul Grime:IOUtils.tobyarray也从内部调用copy方法。正如@Ankit所说,这个解决方案对我无效,因为输入是内部读取的,不能重复使用。我知道这个注释已经过时了,但是,在第一个选项中,如果您将inputstream作为字节数组读取,这是否意味着您正在将所有数据加载到内存中?如果你正在加载像大文件这样的东西,这可能是一个大问题?@jaxkodex,是的,这是正确的。如果您作为一名开发人员更了解您正在处理的流的实际类型,那么您可以编写更合适的自定义行为。提供的答案是一个一般的抽象。可以使用IOUtils.toByteArray(InputStream)在一次调用中获取字节数组。使用
ImageIO#read(java.net.URL)
时需要提醒一句:一些web服务器和CDN可能会拒绝裸调用(即,没有让服务器相信调用来自web浏览器的用户代理)由ImageIO#read
制作。在这种情况下,使用URLConnection.openConnection()
将用户代理设置为该连接+使用`ImageIO.read(InputStream)大多数情况下都可以做到这一点。InputStream
不是接口InputStream不支持'mark'-您可以在is上调用mark,但它什么都不做。同样,在IS上调用reset也会引发异常。@ayahuascaInputStream
子类,如BufferedInputStream
支持“标记”。缓冲输入流只能标记回缓冲区大小,因此如果源代码不合适,则无法返回到开始处。@L.Blanc抱歉,这似乎不正确。查看一下BufferedInputStream.fill(),t
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);
}
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();
}
}
}
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.