web-dev-qa-db-fra.com

Lire le flux deux fois

Comment lisez-vous le même flux d'entrée deux fois? Est-il possible de le copier en quelque sorte?

Je dois obtenir une image sur le Web, la sauvegarder localement, puis renvoyer l'image enregistrée. Je pensais qu'il serait plus rapide d'utiliser le même flux au lieu de créer un nouveau flux avec le contenu téléchargé, puis de le relire.

98
Warpzit

Vous pouvez utiliser org.Apache.commons.io.IOUtils.copy pour copier le contenu du InputStream dans un tableau d'octets, puis le lire à plusieurs reprises à l'aide d'un ByteArrayInputStream. Par exemple.:

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

En fonction de la provenance de InputStream, il se peut que vous ne puissiez pas le réinitialiser. Vous pouvez vérifier si mark() et reset() sont pris en charge à l'aide de markSupported().

Si tel est le cas, vous pouvez appeler reset() sur le flux d’entrée pour revenir au début. Sinon, vous devez relire InputStream à partir de la source.

23
Kevin Parker

Vous pouvez envelopper le flux d'entrée avec PushbackInputStream. PushbackInputStream permet de non lu (" réécrire ") octets déjà lus, vous pouvez donc faire comme ceci:

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


}

Veuillez noter que PushbackInputStream stocke le tampon interne d'octets afin qu'il crée réellement un tampon en mémoire qui contient les octets "écrits en retour".

Connaissant cette approche, nous pouvons aller plus loin et la combiner avec FilterInputStream. FilterInputStream stocke le flux d'entrée d'origine en tant que délégué. Cela permet de créer une nouvelle définition de classe qui permet de " non lu " les données d'origine automatiquement. La définition de cette classe est la suivante:

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

}

Cette classe a deux méthodes. Un pour la lecture dans un tampon existant (la définition est analogue à l'appel de public int read(byte b[], int off, int len) de la classe InputStream). Second qui renvoie un nouveau tampon (cela peut être plus efficace si la taille du tampon à lire est inconnue). 

Voyons maintenant notre classe en action:

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

si votre InputStream prend en charge mark, vous pouvez alors mark() votre inputStream puis reset() le. si votre InputStrem ne prend pas en charge la marque, vous pouvez utiliser la classe Java.io.BufferedInputStream pour intégrer votre flux dans une BufferedInputStream comme ceci

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

Si vous utilisez une implémentation de InputStream , vous pouvez vérifier le résultat de InputStream#markSupported() qui vous indique si vous pouvez ou non utiliser la méthode mark() / reset()

Si vous pouvez marquer le flux lorsque vous lisez, appelez reset() pour revenir en arrière.

Si vous ne pouvez pas, vous devrez ouvrir un flux à nouveau.

Une autre solution serait de convertir InputStream en tableau d'octets, puis de le parcourir autant de fois que nécessaire. Vous pouvez trouver plusieurs solutions dans cet article Convertissez InputStream en tableau d'octets en Java en utilisant des bibliothèques tierces ou non. Attention, si le contenu lu est trop volumineux, vous risquez de rencontrer des problèmes de mémoire.

Enfin, si votre besoin est de lire une image, utilisez:

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

Utiliser ImageIO#read(Java.net.URL) vous permet également d’utiliser le cache.

4
alain.janinm

Convertissez le flux d'entrée en octets, puis transmettez-le à la fonction savefile où vous assemblez-le dans inputstream . Dans la fonction d'origine, utilisez également les octets à utiliser pour d'autres tâches

2
Maneesh

Que diriez-vous:

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

Pour scinder une InputStream en deux, tout en évitant de charger toutes les données en mémoire , puis de les traiter indépendamment:

  1. Créez un couple de OutputStream, précisément: PipedOutputStream
  2. Connectez chaque PipedOutputStream à un PipedInputStream, ces PipedInputStream sont les InputStream retournés.
  3. Connectez le sourçage InputStream à la nouvelle OutputStream. Ainsi, tout ce qui a été lu dans la source InputStream, serait écrit dans les deux OutputStream. Il n’est pas nécessaire de mettre en œuvre cela, car cela est déjà fait dans TeeInputStream (commons.io).
  4. Dans un thread séparé, lisez l'intégralité du flux source inputStream et implicitement, les données d'entrée sont transférées vers le flux cible inputStreams.

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

N'oubliez pas de fermer les inputStreams après avoir été consommés et de fermer le thread qui s'exécute: TeeInputStream.readAllBytes()

Au cas où, vous devez divisez-le en plusieurs InputStream, au lieu de deux. Dans le fragment de code précédent, remplacez la classe TeeOutputStream pour votre propre implémentation, qui encapsulerait un List<OutputStream> et substituerait l'interface OutputStream:

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

Si quelqu'un exécute une application Spring Boot et que vous souhaitez lire le corps de la réponse d'une RestTemplate (c'est pourquoi je veux lire un flux deux fois), il existe un moyen plus propre de le faire.

Tout d'abord, vous devez utiliser la variable StreamUtils de Spring pour copier le flux dans une chaîne:

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

Mais ce n'est pas tout. Vous devez également utiliser une fabrique de demandes pouvant mettre le flux en mémoire tampon pour vous, comme ceci:

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

Ou, si vous utilisez le bean factory, alors (c'est Kotlin mais néanmoins):

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

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

0
milosmns