164 Stimmen

Eingabestrom zweimal lesen

Wie kann man denselben Inputstream zweimal lesen? Ist es möglich, ihn irgendwie zu kopieren?

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

151voto

Paul Grime Punkte 14822

Sie können verwenden org.apache.commons.io.IOUtils.copy um den Inhalt des InputStream in ein Byte-Array zu kopieren und dann wiederholt aus dem Byte-Array mit einem ByteArrayInputStream 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);
}

40voto

Kevin Parker Punkte 16483

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

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

18voto

wannas Punkte 378

Wenn Ihr InputStream Unterstützung durch Markierung, dann können Sie mark() Ihren inputStream und dann reset() es . wenn Ihr InputStrem keine Markierung unterstützt, können Sie die Klasse java.io.BufferedInputStream einbetten, so dass Sie Ihren Stream in eine BufferedInputStream wie diese

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

9voto

walkeros Punkte 4195

Sie können den Eingabestrom mit PushbackInputStream umhüllen. PushbackInputStream ermöglicht ungelesen (" zurückschreiben "), die bereits gelesen wurden, können Sie wie folgt vorgehen:

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 einen internen Puffer von Bytes speichert, also wirklich einen Puffer im Speicher erzeugt, der "zurückgeschriebene" Bytes enthält.

Wenn wir diesen Ansatz kennen, können wir weiter gehen und ihn mit FilterInputStream kombinieren. FilterInputStream speichert den ursprünglichen Eingabestrom als Delegaten. Dies ermöglicht es, eine neue Klassendefinition zu erstellen, die es erlaubt, " ungelesen "Originaldaten automatisch. Die Definition dieser Klasse ist die 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 den vorhandenen Puffer (die Definition ist analog zum Aufruf von public int read(byte b[], int off, int len) der Klasse InputStream). Zweiter, der einen neuen Puffer zurückgibt (dies kann effektiver sein, wenn die Größe des zu lesenden Puffers unbekannt ist).

Nun wollen wir unsere Klasse in Aktion sehen:

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

  }

}

9voto

zeugor Punkte 780

Für die Aufteilung einer InputStream in zwei, ohne dass alle Daten in den Speicher geladen werden und verarbeiten sie dann unabhängig voneinander:

  1. Erstellen Sie ein paar OutputStream , genau: PipedOutputStream
  2. Verbinden Sie jeden PipedOutputStream mit einem PipedInputStream, diese PipedInputStream sind die zurückgegebenen InputStream .
  3. Verbinden Sie den eingehenden InputStream mit dem soeben erstellten OutputStream . Also, alles aus dem Sourcing lesen InputStream würde in beiden Sprachen geschrieben werden OutputStream . Es besteht keine Notwendigkeit, dies zu implementieren, da es bereits in TeeInputStream (commons.io).
  4. Innerhalb eines separaten Threads wird der gesamte Quell-InputStream gelesen, und die Eingabedaten werden implizit 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);
    }

Achten Sie darauf, die inputStreams zu schließen, nachdem sie verbraucht wurden, und schließen Sie den Thread, der läuft: TeeInputStream.readAllBytes()

Für den Fall, dass Sie Folgendes benötigen Aufteilung in mehrere InputStream anstelle von nur zwei. Ersetzen Sie im vorherigen Codefragment die Klasse TeeOutputStream für Ihre eigene Implementierung, die eine List<OutputStream> und überschreiben Sie die OutputStream Schnittstelle:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X