Un moyen facile d'écrire le contenu d'un Java InputStream dans un OutputStream

445

J'ai été surpris de constater aujourd'hui que je ne pouvais pas trouver de moyen simple d'écrire le contenu d'un InputStreaman OutputStreamen Java. De toute évidence, le code tampon d'octets n'est pas difficile à écrire, mais je soupçonne que je manque juste quelque chose qui me faciliterait la vie (et le code plus clair).

Donc, étant donné un InputStream inet un OutputStream out, existe-t-il un moyen plus simple d'écrire ce qui suit?

byte[] buffer = new byte[1024];
int len = in.read(buffer);
while (len != -1) {
    out.write(buffer, 0, len);
    len = in.read(buffer);
}
Matt Sheppard
la source
Vous avez mentionné dans un commentaire qu'il s'agissait d'une application mobile. Est-ce Android natif? Si oui, faites-le moi savoir et je posterai une autre réponse (cela peut être fait en une seule ligne de code dans Android).
Jabari

Réponses:

182

Java 9

Depuis Java 9, InputStreamfournit une méthode appelée transferToavec la signature suivante:

public long transferTo(OutputStream out) throws IOException

Comme les documents états, transferToseront:

Lit tous les octets de ce flux d'entrée et écrit les octets dans le flux de sortie donné dans l'ordre où ils sont lus. Au retour, ce flux d'entrée sera à la fin du flux. Cette méthode ne ferme ni l'un ni l'autre des flux.

Cette méthode peut bloquer indéfiniment la lecture du flux d'entrée ou l'écriture dans le flux de sortie. Le comportement dans le cas où le flux d'entrée et / ou de sortie est fermé de manière asynchrone, ou le thread interrompu pendant le transfert, est très spécifique au flux d'entrée et de sortie, et donc non spécifié

Donc, pour écrire le contenu d'un Java InputStreamdans un OutputStream, vous pouvez écrire:

input.transferTo(output);
Ali Dehghani
la source
11
Vous devriez préférer Files.copyautant que possible. Il est implémenté en code natif et peut donc être plus rapide. transferTone doit être utilisé que si les deux flux ne sont pas FileInputStream / FileOutputStream.
ZhekaKozlov
@ZhekaKozlov Malheureusement, Files.copyne gère aucun flux d'entrée / sortie, mais il est spécialement conçu pour les flux de fichiers .
L'Empaleur
396

Comme l'a mentionné WMR, org.apache.commons.io.IOUtilsApache a une méthode appelée copy(InputStream,OutputStream)qui fait exactement ce que vous recherchez.

Vous avez donc:

InputStream in;
OutputStream out;
IOUtils.copy(in,out);
in.close();
out.close();

... dans votre code.

Y a-t-il une raison que vous évitez IOUtils?

Mikezx6r
la source
170
Je l'évite pour cette application mobile que je suis en train de créer, car cela aurait quintuplé la taille de l'application pour enregistrer 5 maigres lignes de code.
Jeremy Logan
36
cela vaut peut-être la peine de le mentionner inet outdoit être fermé à la fin du code dans un bloc enfin
basZero
24
@basZero Ou en utilisant un bloc try with resources.
Warren Dew
1
Ou vous pouvez simplement écrire votre propre enveloppe de copie (entrée, sortie) ... (en moins de temps pour ...)
MikeM
1
Si vous utilisez déjà la bibliothèque Guava, Andrejs a recommandé la classe ByteStreams ci-dessous. Similaire à ce que fait IOUtils, mais évite d'ajouter Commons IO à votre projet.
Jim Tough
328

Si vous utilisez Java 7, Files (dans la bibliothèque standard) est la meilleure approche:

/* You can get Path from file also: file.toPath() */
Files.copy(InputStream in, Path target)
Files.copy(Path source, OutputStream out)

Edit: Bien sûr, c'est juste utile lorsque vous créez l'un des InputStream ou OutputStream à partir d'un fichier. Utilisez file.toPath()pour obtenir le chemin du fichier.

Pour écrire dans un fichier existant (par exemple celui créé avec File.createTempFile()), vous devrez passer l' REPLACE_EXISTINGoption de copie (sinon elle FileAlreadyExistsExceptionest levée):

Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING)
user1079877
la source
26
Je ne pense pas que cela résout réellement le problème car une extrémité est un chemin. Bien que vous puissiez obtenir un chemin pour un fichier, autant que je sache, vous ne pouvez pas en obtenir un pour un flux générique (par exemple, un sur le réseau).
Matt Sheppard
4
CopyOptions est arbitraire! Vous pouvez le mettre ici si vous le souhaitez.
user1079877
4
voilà ce que je cherchais! JDK à la rescousse, pas besoin d'une autre bibliothèque
Don Cheadle
7
Pour info, Filesn'est PAS disponible dans Java 1.7 d' Android . J'ai été piqué par ceci: stackoverflow.com/questions/24869323/…
Joshua Pinter le
23
De manière amusante, le JDK a également un Files.copy()qui prend deux flux, et c'est ce que toutes les autres Files.copy()fonctions transmettent afin de faire le travail de copie. Cependant, il est privé (car il n'implique pas réellement de chemins ou de fichiers à ce stade) et ressemble exactement au code dans la propre question de l'OP (plus une déclaration de retour). Pas d'ouverture, pas de fermeture, juste une boucle de copie.
Ti Strga
102

Je pense que cela fonctionnera, mais assurez-vous de le tester ... "amélioration" mineure, mais cela pourrait être un peu coûteux en termes de lisibilité.

byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
    out.write(buffer, 0, len);
}
Mike Stone
la source
26
Je suggère un tampon d'au moins 10 Ko à 100 Ko. Ce n'est pas grand-chose et peut accélérer considérablement la copie de grandes quantités de données.
Aaron Digulla
6
vous voudrez peut-être dire à la while(len > 0)place de != -1, car ce dernier peut également retourner 0 lors de l'utilisation de la read(byte b[], int off, int len)méthode -met, qui lève une exception @out.write
phil294
12
@Blauhirn: Ce serait incorrect, car il est tout à fait légal selon le InputStreamcontrat de lire pour retourner 0 autant de fois. Et selon le OutputStreamcontrat, la méthode d'écriture doit accepter une longueur de 0 et ne doit lever une exception que lorsqu'elle lenest négative.
Christoffer Hammarström
1
Vous pouvez enregistrer une ligne en changeant l' whileune foret de mettre l' une des variables pour la section de Init: par exemple for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);. =)
ɲeuroburɳ
1
@Blauhim read()ne peut retourner zéro que si vous avez fourni une longueur de zéro, ce qui serait une erreur de programmation et une condition stupide pour boucler indéfiniment. Et write()ne lève pas d'exception si vous fournissez une longueur nulle.
Marquis de Lorne
54

Utilisation de la goyave ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);
Andrejs
la source
11
N'oubliez pas de fermer les streams après ça!
WonderCsabo
C'est la meilleure réponse si vous utilisez déjà la goyave qui est devenue indispensable pour moi.
Hong
1
@Hong Vous devez utiliser Files.copyautant que possible. À utiliser ByteStreams.copyuniquement si les deux flux ne sont pas FileInputStream / FileOutputStream.
ZhekaKozlov
@ZhekaKozlov Merci pour l'astuce. Dans mon cas, le flux d'entrée provient de la ressource d'une application Android (dessinable).
Hong
26

Fonction simple

Si vous n'en avez besoin que pour écrire un InputStreamdans un, Filevous pouvez utiliser cette fonction simple:

private void copyInputStreamToFile( InputStream in, File file ) {
    try {
        OutputStream out = new FileOutputStream(file);
        byte[] buf = new byte[1024];
        int len;
        while((len=in.read(buf))>0){
            out.write(buf,0,len);
        }
        out.close();
        in.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
Jordan LaPrise
la source
4
Grande fonction, merci. Auriez-vous cependant besoin de mettre les close()appels en finallybloc?
Joshua Pinter du
@JoshPinter Cela ne ferait pas de mal.
Jordan LaPrise
3
Vous devriez probablement à la fois inclure un bloc finally et ne pas avaler d'exceptions dans une implémentation réelle. De plus, la fermeture d'un InputStream passé à une méthode est parfois inattendue par la méthode appelante, il faut donc se demander si c'est le comportement souhaité.
Cel Skeggs
2
Pourquoi intercepter l'exception lorsque IOException suffit?
Prabhakar
18

Le JDKutilise le même code, il semble donc qu'il n'y a pas de moyen "plus facile" sans bibliothèques tierces maladroites (qui ne font probablement rien de différent de toute façon). Ce qui suit est directement copié de java.nio.file.Files.java:

// buffer size used for reading and writing
private static final int BUFFER_SIZE = 8192;

/**
  * Reads all bytes from an input stream and writes them to an output stream.
  */
private static long copy(InputStream source, OutputStream sink) throws IOException {
    long nread = 0L;
    byte[] buf = new byte[BUFFER_SIZE];
    int n;
    while ((n = source.read(buf)) > 0) {
        sink.write(buf, 0, n);
        nread += n;
    }
    return nread;
}
BullyWiiPlaza
la source
2
Toujours. Dommage que cet appel particulier soit privé et il n'y a pas d'autre option que de le copier dans votre propre classe d'utilitaires, car il est possible que vous ne traitez pas avec des fichiers, mais plutôt 2 sockets à la fois.
Dragas
17

PipedInputStreamet PipedOutputStreamne doit être utilisé que lorsque vous avez plusieurs threads, comme indiqué par Javadoc .

Notez également que les flux d'entrée et les flux de sortie n'encapsulent aucune interruption de thread avec IOExceptions ... Donc, vous devriez envisager d'incorporer une politique d'interruption à votre code:

byte[] buffer = new byte[1024];
int len = in.read(buffer);
while (len != -1) {
    out.write(buffer, 0, len);
    len = in.read(buffer);
    if (Thread.interrupted()) {
        throw new InterruptedException();
    }
}

Ce serait un ajout utile si vous prévoyez d'utiliser cette API pour copier de gros volumes de données ou des données de flux qui restent bloqués pendant une durée intolérable.

Dilum Ranatunga
la source
14

Pour ceux qui utilisent Spring Framework, il existe une classe StreamUtils utile :

StreamUtils.copy(in, out);

Ce qui précède ne ferme pas les flux. Si vous souhaitez que les flux soient fermés après la copie, utilisez plutôt la classe FileCopyUtils :

FileCopyUtils.copy(in, out);
holmis83
la source
8

Il n'y a aucun moyen de le faire beaucoup plus facilement avec les méthodes JDK, mais comme Apocalisp l'a déjà noté, vous n'êtes pas le seul à avoir cette idée: vous pouvez utiliser des IOUtils de Jakarta Commons IO , il a aussi beaucoup d'autres choses utiles, que l'OMI devrait en fait faire partie du JDK ...

WMR
la source
6

Utilisant Java7 et try-with-resources , est livré avec une version simplifiée et lisible.

try(InputStream inputStream = new FileInputStream("C:\\mov.mp4");
    OutputStream outputStream = new FileOutputStream("D:\\mov.mp4")) {

    byte[] buffer = new byte[10*1024];

    for (int length; (length = inputStream.read(buffer)) != -1; ) {
        outputStream.write(buffer, 0, length);
    }
} catch (FileNotFoundException exception) {
    exception.printStackTrace();
} catch (IOException ioException) {
    ioException.printStackTrace();
}
Sivakumar
la source
3
Le rinçage à l'intérieur de la boucle est hautement contre-productif.
Marquis de Lorne
5

Voici comment je fais avec le plus simple pour la boucle.

private void copy(final InputStream in, final OutputStream out)
    throws IOException {
    final byte[] b = new byte[8192];
    for (int r; (r = in.read(b)) != -1;) {
        out.write(b, 0, r);
    }
}
Jin Kwon
la source
4

Utilisez la classe Util de Commons Net:

import org.apache.commons.net.io.Util;
...
Util.copyStream(in, out);
DejanLekic
la source
3

Un extrait de mon humble avis plus minimal (qui étend également plus étroitement la variable de longueur):

byte[] buffer = new byte[2048];
for (int n = in.read(buffer); n >= 0; n = in.read(buffer))
    out.write(buffer, 0, n);

En remarque, je ne comprends pas pourquoi plus de gens n'utilisent pas de forboucle, optant plutôt pour un whileavec une expression assign-and-test qui est considérée par certains comme un style "pauvre".

Bohème
la source
1
Votre suggestion provoque une écriture de 0 octet lors de la première itération. Peut-être le moins:for(int n = 0; (n = in.read(buffer)) > 0;) { out.write(buffer, 0, n); }
Brian de Alwis
2
@BriandeAlwis Vous avez raison de dire que la première itération est incorrecte. Le code a été corrigé (à mon humble avis d'une manière plus propre que votre suggestion) - voir le code modifié. Thx pour prendre soin.
Bohème
3

Ceci est mon meilleur coup !!

Et ne l'utilisez pas inputStream.transferTo(...)car il est trop générique. Les performances de votre code seront meilleures si vous contrôlez votre mémoire tampon.

public static void transfer(InputStream in, OutputStream out, int buffer) throws IOException {
    byte[] read = new byte[buffer]; // Your buffer size.
    while (0 < (buffer = in.read(read)))
        out.write(read, 0, buffer);
}

Je l'utilise avec cette méthode (évolutive) quand je connais à l'avance la taille du flux.

public static void transfer(int size, InputStream in, OutputStream out) throws IOException {
    transfer(in, out,
            size > 0xFFFF ? 0xFFFF // 16bits 65,536
                    : size > 0xFFF ? 0xFFF// 12bits 4096
                            : size < 0xFF ? 0xFF // 8bits 256
                                    : size
    );
}
Daniel De León
la source
2

Je pense qu'il vaut mieux utiliser un grand tampon, car la plupart des fichiers sont supérieurs à 1024 octets. Il est également recommandé de vérifier que le nombre d'octets de lecture est positif.

byte[] buffer = new byte[4096];
int n;
while ((n = in.read(buffer)) > 0) {
    out.write(buffer, 0, n);
}
out.close();
Alexander Volkov
la source
4
L'utilisation d'un grand tampon est en effet une bonne idée mais pas parce que les fichiers sont majoritairement> 1k, c'est pour amortir le coût des appels système.
Marquis de Lorne
1

J'utilise BufferedInputStreamet BufferedOutputStreampour supprimer la sémantique de mise en mémoire tampon du code

try (OutputStream out = new BufferedOutputStream(...);
     InputStream in   = new BufferedInputStream(...))) {
  int ch;
  while ((ch = in.read()) != -1) {
    out.write(ch);
  }
}
Archimedes Trajano
la source
Pourquoi «supprimer la sémantique de mise en mémoire tampon du code» est-il une bonne idée?
Marquis de Lorne le
2
Cela signifie que je n'écris pas moi-même la logique de mise en mémoire tampon, j'utilise celle intégrée au JDK qui est généralement assez bonne.
Archimedes Trajano
0

PipedInputStream et PipedOutputStream peuvent être d'une certaine utilité, car vous pouvez vous connecter l'un à l'autre.

Arktronic
la source
1
Ce n'est pas bon pour le code à thread unique car cela pourrait bloquer; voir cette question stackoverflow.com/questions/484119/…
Raekye
2
Peut être utile de quelle façon? Il a déjà un flux d'entrée et un flux de sortie. Comment l'ajout d'un autre de chaque aide exactement?
Marquis de Lorne
0

Un autre candidat possible sont les utilitaires d'E / S Guava:

http://code.google.com/p/guava-libraries/wiki/IOExplained

J'ai pensé les utiliser car Guava est déjà extrêmement utile dans mon projet, plutôt que d'ajouter une autre bibliothèque pour une fonction.

Andrew Mao
la source
Il existe copyet toByteArrayméthodes docs.guava-libraries.googlecode.com/git-history/release/javadoc/... (goyave appels flux d' entrée / sortie en tant que « flux d'octets » et les lecteurs / auteurs comme « flux char »)
Raekye
si vous utilisez déjà des bibliothèques de goyaves, c'est une bonne idée, mais sinon, elles sont une bibliothèque gigantesque avec des milliers de méthodes «google-way-of-do-everything-different-to-the-standard». Je
resterais
"mammouth"? 2,7 Mo avec un très petit ensemble de dépendances et une API qui évite soigneusement de dupliquer le noyau JDK.
Adrian Baker
0

Pas très lisible, mais efficace, sans dépendances et fonctionne avec n'importe quelle version java

byte[] buffer = new byte[1024];
for (int n; (n = inputStream.read(buffer)) != -1; outputStream.write(buffer, 0, n));
IPP Nerd
la source
!= -1ou > 0? Ces prédicats ne sont pas tout à fait les mêmes.
L'Empaleur
! = -1 signifie pas de fin de fichier. Ce n'est pas une itération mais une boucle while-do-déguisée: while ((n = inputStream.read (buffer))! = -1) do {outputStream.write (buffer, 0, n)}
IPP Nerd
-1
public static boolean copyFile(InputStream inputStream, OutputStream out) {
    byte buf[] = new byte[1024];
    int len;
    long startTime=System.currentTimeMillis();

    try {
        while ((len = inputStream.read(buf)) != -1) {
            out.write(buf, 0, len);
        }

        long endTime=System.currentTimeMillis()-startTime;
        Log.v("","Time taken to transfer all bytes is : "+endTime);
        out.close();
        inputStream.close();

    } catch (IOException e) {

        return false;
    }
    return true;
}
Nour Rteil
la source
4
Pouvez-vous expliquer pourquoi c'est la bonne réponse?
rfornal le
-6

vous pouvez utiliser cette méthode

public static void copyStream(InputStream is, OutputStream os)
 {
     final int buffer_size=1024;
     try
     {
         byte[] bytes=new byte[buffer_size];
         for(;;)
         {
           int count=is.read(bytes, 0, buffer_size);
           if(count==-1)
               break;
           os.write(bytes, 0, count);
         }
     }
     catch(Exception ex){}
 }
Pranav
la source
6
catch(Exception ex){}- c'est top
ᄂ ᄀ