2012-02-29 24 views
80

Come si legge lo stesso inputstream due volte? È possibile copiarlo in qualche modo?Leggi lo stream due volte

Ho bisogno di ottenere un'immagine dal web, salvarla localmente e quindi restituire l'immagine salvata. Ho solo pensato che sarebbe stato più veloce utilizzare lo stesso flusso invece di avviare un nuovo stream per il contenuto scaricato e quindi leggerlo di nuovo.

+0

Forse utilizzare il marchio e ripristinare –

risposta

73

È possibile utilizzare org.apache.commons.io.IOUtils.copy per copiare il contenuto del InputStream a un array di byte, e poi ripetutamente leggere l'array di byte utilizzando un ByteArrayInputStream. Es .:

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

Penso che questa sia l'unica soluzione valida poiché mark non è supportato per tutti i tipi. – Warpzit

+3

@Paul Grime: IOUtils.toByeArray chiama internamente anche il metodo di copia dall'interno. – Ankit

+1

Come dice @Ankit, questa soluzione non è valida per me, dal momento che l'input viene letto internamente e non può essere riutilizzato. –

20

A seconda della provenienza di InputStream, potrebbe non essere possibile ripristinarlo. È possibile verificare se mark() e reset() sono supportati utilizzando markSupported().

In caso affermativo, è possibile chiamare reset() su InputStream per tornare all'inizio. In caso contrario, è necessario leggere nuovamente InputStream dalla sorgente.

+0

InputStream non supporta 'marchio' - è possibile chiamare segno su un IS ma non fa nulla. Allo stesso modo, chiamando reset su un IS genererà un'eccezione. – ayahuasca

4

Se si utilizza un'implementazione di InputStream, è possibile controllare il risultato di InputStream#markSupported() che dire se non è possibile utilizzare il metodo mark()/reset().

Se è possibile contrassegnare il flusso quando si legge, quindi chiamare reset() per tornare indietro per iniziare.

Se non riesci, dovrai aprire di nuovo uno stream.

Un'altra soluzione potrebbe essere quella di convertire InputStream in array di byte, quindi scorrere l'array per tutto il tempo necessario. Puoi trovare diverse soluzioni in questo post Convert InputStream to byte array in Java usando libs di terze parti o meno. Attenzione, se il contenuto letto è troppo grande si potrebbero verificare alcuni problemi di memoria.

Infine, se la vostra necessità è quella di leggere l'immagine, quindi utilizzare:

BufferedImage image = ImageIO.read(new URL("http://www.example.com/images/toto.jpg")); 

Utilizzando ImageIO#read(java.net.URL) permette inoltre di utilizzare la cache.

+1

una parola di avvertimento quando si usa 'ImageIO # read (java.net.URL)': alcuni server web e CDN potrebbero rifiutare chiamate nude (cioè senza un agente utente che fa credere al server che la chiamata provenga da un browser web) fatte da ' ImageIO # read'. In quel caso, usando 'URLConnection.openConnection()' impostando l'agente utente su quella connessione + usando 'ImageIO.read (InputStream), la maggior parte delle volte, farà il trucco. –

+0

'InputStream' non è un'interfaccia – Brice

+0

@Brice Anzi, grazie per averlo indicato! –

2

Convertire l'inputstream in byte e quindi passarlo alla funzione savefile dove si assemblano gli stessi in inputstream. anche in uso funzione originaria byte da utilizzare per altre attività

+3

Dico cattiva idea su questo, la matrice risultante potrebbe essere enorme e ruberà il dispositivo della memoria. –

7

se il vostro sostegno InputStream utilizzando marchio, allora si può mark() tuo inputStream e poi reset() esso. se il vostro InputStrem non supporta marchio quindi è possibile utilizzare la classe java.io.BufferedInputStream, in modo da poter incorporare il flusso all'interno di un BufferedInputStream come questo

InputStream bufferdInputStream = new BufferedInputStream(yourInputStream); 
    bufferdInputStream.mark(some_value); 
    //read your bufferdInputStream 
    bufferdInputStream.reset(); 
    //read it again 
7

Puoi avvolgere flusso di input con PushbackInputStream. PushbackInputStream permette di ("indietroscrivere") byte da leggere che erano già letto, in modo da poter fare in questo modo:

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(); 
    } 


} 

Si prega di notare che i negozi PushbackInputStream buffer interno di byte in modo che davvero crea un buffer in memoria che contiene i byte "riscritti".

Conoscendo questo approccio possiamo andare oltre e combinarlo con FilterInputStream.FilterInputStream memorizza il flusso di input originale come delegato. Ciò consente di creare una nuova definizione di classe che consente di "i dati originali non letti" automaticamente non letti. La definizione di questa classe è la seguente:

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); 
    } 
    } 

} 

Questa classe ha due metodi. Uno per la lettura nel buffer esistente (la definizione è analoga a chiamare public int read(byte b[], int off, int len) della classe InputStream). Secondo che restituisce un nuovo buffer (questo potrebbe essere più efficace se la dimensione del buffer da leggere è sconosciuta).

Ora vediamo la nostra classe in azione:

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

ne dite:

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' 

    } 
Problemi correlati