187 votes

Comment cloner un InputStream ?

J'ai un InputStream que je passe à une méthode pour effectuer un traitement. Je vais utiliser le même InputStream dans une autre méthode, mais après le premier traitement, l'InputStream semble être fermé dans la méthode.

Comment je peux cloner l'InputStream pour l'envoyer à la méthode qui le ferme ? Il existe une autre solution ?

EDIT : la méthode qui ferme l'InputStream est une méthode externe d'une librairie. 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";
    }
}

2 votes

Voulez-vous "réinitialiser" le flux après le retour de la méthode ? C'est-à-dire lire le flux depuis le début ?

0 votes

Oui, la méthode qui ferme l'InputStream renvoie le jeu de caractères qui a été encodé. La deuxième méthode consiste à convertir l'InputStream en String en utilisant le charset trouvé dans la première méthode.

0 votes

Dans ce cas, vous devriez être en mesure de faire ce que je décris dans ma réponse.

220voto

Anthony Accioly Points 10501

Si tout ce que vous voulez faire est de lire la même information plus d'une fois, et que les données d'entrée sont assez petites pour tenir dans la mémoire, vous pouvez copier les données de votre InputStream a un Tableau d'octets de sortie (ByteArrayOutputStream) .

Vous pouvez ensuite obtenir le tableau d'octets associé et ouvrir autant de "clones" que nécessaire. Flux d'entrée ByteArrayInputStream comme vous le souhaitez.

ByteArrayOutputStream baos = new ByteArrayOutputStream();

// Code simulating the copy
// You could alternatively use NIO
// 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 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 avez vraiment besoin de garder le flux original ouvert pour recevoir de nouvelles données, alors vous devrez suivre l'appel externe à close() . Vous devrez empêcher close() d'être appelé d'une manière ou d'une autre.

MISE À JOUR (2019) :

Depuis Java 9, les bits du milieu peuvent être remplacés par InputStream.transferTo :

ByteArrayOutputStream baos = new ByteArrayOutputStream();
input.transferTo(baos);
InputStream firstClone = new ByteArrayInputStream(baos.toByteArray()); 
InputStream secondClone = new ByteArrayInputStream(baos.toByteArray());

0 votes

J'ai trouvé une autre solution à mon problème qui n'implique pas de copier l'InputStream, mais je pense que si je dois copier l'InputStream, c'est la meilleure solution.

11 votes

Cette approche consomme de la mémoire proportionnellement au contenu complet du flux d'entrée. Il est préférable d'utiliser TeeInputStream comme décrit dans la réponse sur aquí .

2 votes

IOUtils (de apache commons) a une méthode de copie qui ferait la lecture/écriture du tampon au milieu de votre code.

34voto

Femi Points 42054

Vous voulez utiliser la fonction CloseShieldInputStream :

Il s'agit d'une enveloppe 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();

0 votes

Ça a l'air bien, mais ça ne marche pas ici. Je vais éditer mon post avec le code.

0 votes

CloseShield ne fonctionne pas parce que l'original HttpURLConnection Le flux d'entrée est fermé quelque part. Votre méthode ne devrait-elle pas appeler IOUtils avec le flux protégé ? IOUtils.toString(csContent,charset) ?

0 votes

Peut-être que ça peut être ça. Je peux empêcher la fermeture de la HttpURLConnection ?

14voto

Kaj Points 6802

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 l'InputStream dans un tableau d'octets, puis à créer un ByteArrayInputStream autour de ce tableau d'octets, et à passer ce flux d'entrée dans votre méthode.

Edit 1 : C'est-à-dire, si l'autre méthode doit également lire les mêmes données. C'est-à-dire que vous voulez "réinitialiser" le flux.

0 votes

Je ne sais pas pour quelle partie vous avez besoin d'aide. Je suppose que vous savez comment lire à partir d'un flux ? Lisez toutes les données de l'InputStream, et écrivez les données dans le ByteArrayOutputStream. Appelez toByteArray() sur le ByteArrayOutputStream une fois que vous avez terminé de lire toutes les données. Passez ensuite ce tableau d'octets dans le constructeur d'un ByteArrayInputStream.

11voto

Nathan Ryan Points 5623

Si les données lues à partir du flux sont importantes, je recommande d'utiliser un TeeInputStream de Apache Commons IO. De cette façon, vous pouvez essentiellement répliquer l'entrée et passer un tube t'd comme clone.

6voto

Diederik Points 985

Cela peut ne pas fonctionner dans toutes les situations, mais voici ce que j'ai fait : J'ai étendu le FilterInputStream et effectue le traitement nécessaire des octets pendant que 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é le flux d'entrée. Avec le flux d'entrée original comme paramètre du constructeur.

Il convient de noter que cette méthode fonctionne octet par octet, donc ne l'utilisez pas si vous avez besoin de hautes performances.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X