2012-02-29 74 views
80

如何读取两次相同的输入流?是否有可能以某种方式复制它?两次读取流

我需要从网上获取图片,将其保存到本地,然后返回保存的图片。我只是想要使用相同的流而不是开始一个新的流到下载的内容然后再次读取。

+0

也许使用mark和reset – 2012-02-29 14:54:26

回答

73

您可以使用org.apache.commons.io.IOUtils.copy的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); 
} 
+1

我认为这是唯一有效的解决方案,因为所有类型都不支持标记。 – Warpzit 2012-03-01 11:10:50

+3

@Paul Grime:IOUtils.toByeArray也从内部调用复制方法。 – Ankit 2012-04-17 09:13:16

+1

正如@Ankit所说,这个解决方案对我来说无效,因为输入是在内部读取的,不能重复使用。 – 2014-05-29 14:12:45

20

根据InputStream的来源,您可能无法重置它。您可以使用markSupported()来检查是否支持mark()reset()

如果是,您可以在InputStream上调用reset()返回到开头。如果不是,则需要再次从源读取InputStream。

+0

的InputStream不支持 '标记' - 你可以调用上的IS标志但它什么都不做。同样,在一个IS上调用reset会引发异常。 – ayahuasca 2017-09-05 13:18:31

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),您还可以使用缓存。

+1

当使用'ImageIO#read(java.net.URL)'时,出现一个警告词:一些网络服务器和CDN可能拒绝裸机调用(即没有使服务器认为调用来自web浏览器的用户代理) ImageIO的#read'。在这种情况下,使用'ImageUI.read(InputStream)'使用'URLConnection.openConnection()'将用户代理设置为该连接+将会大部分时间都会发挥作用。 – 2017-08-10 19:12:12

+0

'InputStream'不是接口 – Brice 2017-11-30 12:58:04

+0

@Brice的确,感谢您指出这一点! – 2017-11-30 15:00:13

2

将输入流转换为字节,然后将其传递给savefile函数,并将其汇编到输入流中。 此外,在原有功能的使用字节用于其他任务

+3

我说这个不好主意,由此产生的数组可能会很大,并会抢夺内存设备。 – 2012-03-09 20:30:54

7

如果使用标记的InputStream支持,那么你可以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 
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商店内部缓冲区,所以它确实创造了一个缓冲保存字节“回写”的内存。

了解这种方法,我们可以进一步将其与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); 
    } 
    } 

} 

该类有两种方法。一个用于读入现有缓冲区(定义类似于调用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 


    } 



} 
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' 

    }