2012-02-29 10 views
80

どのように同じ入力ストリームを2回読むのですか?何とかコピーすることは可能ですか?ストリームを2回読み取る

ウェブから画像を取得し、ローカルに保存してから保存した画像を返す必要があります。ダウンロードしたコンテンツの新しいストリームを開始するのではなく、同じストリームを使用して再度読み込むほうが速くて済みます。

+0

はたぶん「マーク」をサポートしていないマークを使用して –

答えて

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

+3

@Paul Grime:IOUtils.toByeArrayは内部的にもコピーメソッドを内部的に呼び出します。 – Ankit

+1

@Ankitによると、入力は内部的に読み込まれ再利用できないので、この解決策は私にとっては有効ではありません。 –

20

InputStreamの送信元によっては、リセットできないことがあります。 markSupported()を使用してmark()reset()がサポートされているかどうかを確認できます。

この場合、InputStreamのreset()を呼び出して先頭に戻ることができます。そうでなければ、ソースからInputStreamを再度読み取る必要があります。

+0

InputStreamをリセットする - あなたはISにマークを呼び出すことができますそれは何もしません。同様に、ISでresetを呼び出すと例外がスローされます。 – ayahuasca

4

あなたがInputStreamの実装を使用している場合、あなたはメソッドmark()/reset()を使用できるかどうかを教えてくれInputStream#markSupported()の結果を確認することができます。

読み込み時にストリームにマークを付けることができる場合は、reset()に電話をかけて始めてください。

できない場合は、もう一度ストリームを開く必要があります。

もう1つの解決策は、InputStreamをバイト配列に変換し、必要に応じて配列を繰り返し処理することです。このポストConvert InputStream to byte array in Javaには、サードパーティ製のライブラリを使用しているかどうかにかかわらず、いくつかのソリューションがあります。注意してください。読み込みコンテンツが大きすぎると、メモリの問題が発生する可能性があります。最後に

、あなたの必要性は、画像を読み取ることであれば、次に使用:ImageIO#read(java.net.URL)を使用して

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

はまた、あなたがキャッシュを使用することができます。

+1

'ImageIO#read(java.net.URL)'を使用した場合の警告メッセージ:WebサーバやCDNの中には、裸の呼び出しを拒否するものがあります(つまり、Webブラウザからの呼び出しであるとサーバに信じさせるユーザエージェントはありません) ImageIO#read'。その場合、 'ImageIO.read(InputStream) 'を使用して、その接続にユーザエージェントを設定する' URLConnection.openConnection() 'を使用して、ほとんどの場合、トリックを行います。 –

+0

'InputStream'はインターフェイスではありません – Brice

+0

@Briceこれを指摘してくれてありがとう! –

2

inputstreamをバイトに変換し、それをsavefile関数に渡します。ここで、入力ストリームに組み込みます。 元の関数でも、他のタスクで使用するバイトを使用します。

+3

私はこれについて悪い考え方をしています。結果として得られる配列は膨大で、メモリのデバイスを奪うでしょう。 –

7

マークを使用してInputStreamサポートしている場合、あなたはそれをあなたのInputStreamをmark()、その後reset()ことができます。あなたのInputStremマークをサポートしていない場合は、この

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

ようBufferedInputStreamの内側にあなたのストリームを埋め込むことができますので、あなたはあなたがPushbackInputStreamで入力ストリームをラップすることができ、クラスjava.io.BufferedInputStreamを使用することができます。それは本当にバッファ内に作成されますので

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店の内部バッファに注意してください:あなたはこのように行うことができますので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); 
    } 
    } 

} 

このクラスには2つのメソッドがあります。既存のバッファに読み込むためのもの(定義はInputStreamクラスのpublic int read(byte b[], int off, int len)を呼び出すのと同様です)。 2番目は新しいバッファを返します(これは、読み込むバッファのサイズが不明な場合に有効です)。

今度はアクションで私たちのクラスを見てみましょう:

についてどのように
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' 

    } 
関連する問題