2012-02-29 134 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' 

    }