webentwicklung-frage-antwort-db.com.de

Stream zweimal lesen

Wie liest man denselben Inputstream zweimal? Kann man es irgendwie kopieren?

Ich muss ein Bild aus dem Internet abrufen, lokal speichern und dann das gespeicherte Bild zurückgeben. Ich habe nur gesagt, es wäre schneller, den gleichen Stream zu verwenden, anstatt einen neuen Stream mit dem heruntergeladenen Inhalt zu starten und ihn dann erneut zu lesen.

98
Warpzit

Sie können org.Apache.commons.io.IOUtils.copy verwenden, um den Inhalt des InputStream in ein Bytearray zu kopieren und dann mit einem ByteArrayInputStream wiederholt aus dem Bytearray zu lesen. Z.B.:

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);
}
86
Paul Grime

Je nachdem, woher der InputStream kommt, können Sie ihn möglicherweise nicht zurücksetzen. Mit mark() können Sie prüfen, ob reset() und markSupported() unterstützt werden.

Ist dies der Fall, können Sie reset() auf dem InputStream aufrufen, um zum Anfang zurückzukehren. Wenn nicht, müssen Sie den InputStream erneut von der Quelle lesen.

23
Kevin Parker

Sie können den Eingabestream mit PushbackInputStream umschließen. PushbackInputStream erlaubt das Lesen von unread (" write back ") Bytes, die bereits gelesen wurden, so dass Sie dies wie folgt tun können:

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


}

Bitte beachten Sie, dass PushbackInputStream den internen Puffer von Bytes speichert, sodass wirklich ein Puffer im Speicher erstellt wird, der die "zurückgeschriebenen" Bytes enthält.

Wenn wir diesen Ansatz kennen, können wir noch weiter gehen und ihn mit FilterInputStream kombinieren. FilterInputStream speichert den ursprünglichen Eingabestrom als Stellvertreter. Dies ermöglicht das Erstellen einer neuen Klassendefinition, mit der die Originaldaten automatisch " ungelesen " werden können. Die Definition dieser Klasse ist folgende:

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

}

Diese Klasse hat zwei Methoden. Eine zum Einlesen in einen vorhandenen Puffer (Definition ist analog zum Aufruf von public int read(byte b[], int off, int len) der InputStream-Klasse). Zweite, die einen neuen Puffer zurückgibt (dies ist möglicherweise effektiver, wenn die Größe des zu lesenden Puffers unbekannt ist). 

Nun sehen wir unsere Klasse in Aktion:

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


  }



}
8
walkeros

wenn Ihre InputStream Marke unterstützt, können Sie mark() Ihren inputStream und dann reset() damit. Wenn Ihre InputStrem keine Markierung unterstützt, können Sie die Klasse Java.io.BufferedInputStream verwenden, sodass Sie Ihren Stream so in eine BufferedInputStream einbetten können

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

Wenn Sie eine Implementierung von InputStream verwenden, können Sie das Ergebnis von InputStream#markSupported() überprüfen, um festzustellen, ob Sie die Methode mark() // reset() verwenden. 

Wenn Sie den Stream beim Lesen markieren können, rufen Sie reset() auf, um zum Anfang zurückzukehren.

Wenn nicht, müssen Sie einen Stream erneut öffnen.

Eine andere Lösung wäre, InputStream in ein Byte-Array zu konvertieren und dann so oft wie nötig über das Array zu iterieren. In diesem Beitrag finden Sie mehrere Lösungen Konvertieren Sie InputStream in ein Byte-Array in Java mit Drittanbieter-Bibliotheken oder nicht. Vorsicht, wenn der gelesene Inhalt zu groß ist, können Speicherprobleme auftreten.

Wenn Sie ein Bild lesen möchten, verwenden Sie schließlich:

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

Mit ImageIO#read(Java.net.URL) können Sie auch den Cache verwenden.

4
alain.janinm

Konvertieren Sie den Eingabestrom in Bytes und übergeben Sie ihn an die savefile-Funktion, wo Sie ihn in den Eingabestrom einfügen. Auch in der ursprünglichen Funktion werden Bytes für andere Aufgaben verwendet

2
Maneesh

Wie wäre es mit:

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'

    }
2

Um einen InputStreamin zwei Teile zu teilen, und dabei zu vermeiden, alle Daten im Speicher zu laden und sie dann unabhängig zu verarbeiten:

  1. Erstellen Sie ein paar OutputStreamname__, genau: PipedOutputStreamname__
  2. Verbinden Sie jeden PipedOutputStream mit einem PipedInputStream. Diese PipedInputStreamsind die zurückgegebenen InputStreamname__.
  3. Verbinden Sie den Sourcing-InputStream mit dem gerade erstellten OutputStreamname__. Also würde alles, was es aus dem Sourcing InputStreamgelesen hat, in beiden OutputStreamgeschrieben werden. Dies muss nicht implementiert werden, da dies bereits in TeeInputStream(commons.io) erfolgt.
  4. Lesen Sie innerhalb eines getrennten Threads den gesamten Sourcing-InputStream, und implizit werden die Eingabedaten an die Ziel-InputStreams übertragen.

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

Denken Sie daran, die inputStreams nach dem Verzehr zu schließen und den ausgeführten Thread zu schließen: TeeInputStream.readAllBytes()

In diesem Fall müssen Sie statt nur zwei in mehrere InputStreamaufteilen. Ersetzen Sie im vorherigen Codefragment die Klasse TeeOutputStreamfür Ihre eigene Implementierung, die einen List<OutputStream> einkapselt und die Schnittstelle OutputStreamüberschreibt:

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

Falls jemand in einer Spring Boot-App ausgeführt wird und Sie den Antworttext einer RestTemplate lesen möchten (weshalb ich einen Stream zweimal lesen möchte), gibt es eine saubere Methode.

Zunächst müssen Sie Spring's StreamUtils verwenden, um den Stream in einen String zu kopieren:

String text = StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()))

Aber das ist nicht alles. Sie müssen auch eine Anforderungsfactory verwenden, die den Stream für Sie puffern kann, z.

ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate(factory);

Oder, wenn Sie die Factory-Bean verwenden, dann (dies ist Kotlin aber trotzdem):

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
fun createRestTemplate(): RestTemplate = RestTemplateBuilder()
  .requestFactory { BufferingClientHttpRequestFactory(SimpleClientHttpRequestFactory()) }
  .additionalInterceptors(loggingInterceptor)
  .build()

Quelle: https://objectpartners.com/2018/03/01/log-ihr-resttemplate-request-and-response-without-destroying-the-body/

0
milosmns