web-dev-qa-db-fra.com

Comment cloner un InputStream?

J'ai un InputStream que je passe à une méthode pour effectuer certains traitements. Je vais utiliser le même InputStream dans une autre méthode, mais après le premier traitement, le InputStream apparaît être fermé à l'intérieur de la méthode.

Comment je peux cloner le InputStream à envoyer à la méthode qui le ferme? Il y a une autre solution?

EDIT: la méthode qui ferme le InputStream est une méthode externe à partir d'une lib. Je n'ai pas le contrôle de la fermeture ou non.

private String getContent(HttpURLConnection con) {
    InputStream content = null;
    String charset = "";
    try {
        content = con.getInputStream();
        CloseShieldInputStream csContent = new CloseShieldInputStream(content);
        charset = getCharset(csContent);            
        return  IOUtils.toString(content,charset);
    } catch (Exception e) {
        System.out.println("Error downloading page: " + e);
        return null;
    }
}

private String getCharset(InputStream content) {
    try {
        Source parser = new Source(content);
        return parser.getEncoding();
    } catch (Exception e) {
        System.out.println("Error determining charset: " + e);
        return "UTF-8";
    }
}
138
Renato Dinhani

Si vous souhaitez uniquement lire les mêmes informations plusieurs fois et que les données d'entrée sont suffisamment petites pour tenir en mémoire, vous pouvez copier les données de votre InputStream dans un ByteArrayOutputStream .

Ensuite, vous pouvez obtenir le tableau d'octets associé et ouvrir autant de "clonés" ByteArrayInputStream s que vous le souhaitez.

ByteArrayOutputStream baos = new ByteArrayOutputStream();

// Fake code simulating the copy
// You can generally do better with nio if you need...
// And please, unlike me, do something about the Exceptions :D
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) > -1 ) {
    baos.write(buffer, 0, len);
}
baos.flush();

// Open new InputStreams using the recorded bytes
// Can be repeated as many times as you wish
InputStream is1 = new ByteArrayInputStream(baos.toByteArray()); 
InputStream is2 = new ByteArrayInputStream(baos.toByteArray()); 

Mais si vous devez vraiment garder le flux original ouvert pour recevoir de nouvelles données, vous devrez suivre cette méthode externe close() et l'empêcher d'être appelée d'une manière ou d'une autre. 

162
Anthony Accioly

Vous voulez utiliser CloseShieldInputStream

C'est un wrapper qui empêchera la fermeture du flux. Vous feriez quelque chose comme ça.

InputStream is = null;

is = getStream(); //obtain the stream 
CloseShieldInputStream csis = new CloseShieldInputStream(is);

// call the bad function that does things it shouldn't
badFunction(csis);

// happiness follows: do something with the original input stream
is.read();
30
Femi

Vous ne pouvez pas le cloner, et la façon dont vous allez résoudre votre problème dépend de la source des données. 

Une solution consiste à lire toutes les données de InputStream dans un tableau d'octets, puis à créer un ByteArrayInputStream autour de ce tableau d'octets et à transmettre ce flux d'entrée à votre méthode.

Edit 1: Autrement dit, si l’autre méthode doit également lire les mêmes données. Ie vous voulez "réinitialiser" le flux.

9
Kaj

Si les données lues dans le flux sont volumineuses, je vous recommanderais d'utiliser un flux TeeInputStream d'Apache Commons IO. De cette façon, vous pouvez essentiellement répliquer l’entrée et passer un canal t'd comme clone.

6
Nathan Ryan

Si vous utilisez Apache.commons, vous pouvez copier des flux en utilisant IOUtils.

Vous pouvez utiliser le code suivant:

InputStream = IOUtils.toBufferedInputStream(toCopy);

Voici l'exemple complet adapté à votre situation:

public void cloneStream() throws IOException{
    InputStream toCopy=IOUtils.toInputStream("aaa");
    InputStream dest= null;
    dest=IOUtils.toBufferedInputStream(toCopy);
    toCopy.close();
    String result = new String(IOUtils.toByteArray(dest));
    System.out.println(result);
}

Ce code nécessite des dépendances:

MAVEN

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

GRADLE

'commons-io:commons-io:2.4'

Voici la référence DOC pour cette méthode:

Récupère tout le contenu d'un InputStream et représente les mêmes données que résultat InputStream. Cette méthode est utile où,

Source InputStream est lent. Il a des ressources réseau associées, alors nous ne peut pas le garder ouvert pendant longtemps. Il a un délai d'attente réseau associé.

Vous trouverez plus d'informations sur IOUtils ici: http://commons.Apache.org/proper/commons-io/javadocs/api-2.4/org/Apache/commons/io/IOUtils.html#toBufferedInputStream(Java .io.InputStream)

4
Andrey E

Cela peut ne pas fonctionner dans toutes les situations, mais voici ce que j’ai fait: j’ai étendu la FilterInputStream class et fait le traitement requis des octets lorsque la bibliothèque externe lit les données. 

public class StreamBytesWithExtraProcessingInputStream extends FilterInputStream {

    protected StreamBytesWithExtraProcessingInputStream(InputStream in) {
        super(in);
    }

    @Override
    public int read() throws IOException {
        int readByte = super.read();
        processByte(readByte);
        return readByte;
    }

    @Override
    public int read(byte[] buffer, int offset, int count) throws IOException {
        int readBytes = super.read(buffer, offset, count);
        processBytes(buffer, offset, readBytes);
        return readBytes;
    }

    private void processBytes(byte[] buffer, int offset, int readBytes) {
       for (int i = 0; i < readBytes; i++) {
           processByte(buffer[i + offset]);
       }
    }

    private void processByte(int readByte) {
       // TODO do processing here
    }

}

Ensuite, vous passez simplement une instance de StreamBytesWithExtraProcessingInputStream où vous auriez passé dans le flux d'entrée. Avec le flux d'entrée d'origine en tant que paramètre constructeur.

Il est à noter que cela fonctionne octet par octet. Ne l'utilisez donc pas si une performance élevée est requise.

4
Diederik

Cloner un flux d'entrée peut ne pas être une bonne idée, car cela nécessite une connaissance approfondie des détails du flux d'entrée en cours de clonage. Une solution de contournement consiste à créer un nouveau flux d'entrée lisant à nouveau à partir de la même source.

Donc, en utilisant certaines fonctionnalités de Java 8, ceci ressemblerait à ceci:

public class Foo {

    private Supplier<InputStream> inputStreamSupplier;

    public void bar() {
        procesDataThisWay(inputStreamSupplier.get());
        procesDataTheOtherWay(inputStreamSupplier.get());
    }

    private void procesDataThisWay(InputStream) {
        // ...
    }

    private void procesDataTheOtherWay(InputStream) {
        // ...
    }
}

Cette méthode a pour effet positif de réutiliser le code déjà en place - la création du flux d'entrée encapsulé dans inputStreamSupplier. Et il n'est pas nécessaire de conserver un deuxième chemin de code pour le clonage du flux.

D'autre part, si la lecture du flux est coûteuse (car elle est effectuée sur une connexion à faible bande passante), cette méthode doublera les coûts. Cela pourrait être contourné en utilisant un fournisseur spécifique qui stockera d'abord le contenu du flux localement et fournira une InputStream pour cette ressource désormais locale.

0
SpaceTrucker

La classe ci-dessous devrait faire l'affaire. Créez simplement une instance, appelez la méthode "multiply" et indiquez le flux d'entrée source et la quantité de doublons dont vous avez besoin.

Important: vous devez utiliser tous les flux clonés simultanément dans des threads distincts.

package foo.bar;

import Java.io.IOException;
import Java.io.InputStream;
import Java.io.PipedInputStream;
import Java.io.PipedOutputStream;
import Java.util.concurrent.ExecutorService;
import Java.util.concurrent.Executors;

public class InputStreamMultiplier {
    protected static final int BUFFER_SIZE = 1024;
    private ExecutorService executorService = Executors.newCachedThreadPool();

    public InputStream[] multiply(final InputStream source, int count) throws IOException {
        PipedInputStream[] ins = new PipedInputStream[count];
        final PipedOutputStream[] outs = new PipedOutputStream[count];

        for (int i = 0; i < count; i++)
        {
            ins[i] = new PipedInputStream();
            outs[i] = new PipedOutputStream(ins[i]);
        }

        executorService.execute(new Runnable() {
            public void run() {
                try {
                    copy(source, outs);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

        return ins;
    }

    protected void copy(final InputStream source, final PipedOutputStream[] outs) throws IOException {
        byte[] buffer = new byte[BUFFER_SIZE];
        int n = 0;
        try {
            while (-1 != (n = source.read(buffer))) {
                //write each chunk to all output streams
                for (PipedOutputStream out : outs) {
                    out.write(buffer, 0, n);
                }
            }
        } finally {
            //close all output streams
            for (PipedOutputStream out : outs) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
0
vstrom coder

Voici la solution avec Kotlin.

Vous pouvez copier votre InputStream dans ByteArray

val inputStream = ...

val byteOutputStream = ByteArrayOutputStream()
inputStream.use { input ->
    byteOutputStream.use { output ->
        input.copyTo(output)
    }
}

val byteInputStream = ByteArrayInputStream(byteOutputStream.toByteArray())

Si vous devez lire plusieurs fois la byteInputStream, appelez byteInputStream.reset() avant de relire.

https://code.luasoftware.com/tutorials/kotlin/how-to-clone-inputstream/

0
Desmond Lua