web-dev-qa-db-fra.com

Meilleure façon de diriger InputStream vers OutputStream

J'essayais de trouver la meilleure façon de diriger le InputStream vers OutputStream. Je n'ai pas la possibilité d'utiliser d'autres bibliothèques comme Apache IO. Voici l'extrait et la sortie.

import Java.io.FileInputStream;
import Java.io.FileOutputStream;
import Java.io.IOException;
import Java.io.InputStream;
import Java.io.OutputStream;
import Java.nio.channels.FileChannel;

public class Pipe {
    public static void main(String[] args) throws Exception {

        for(PipeTestCase testCase : testCases) {
            System.out.println(testCase.getApproach());
            InputStream is = new FileInputStream("D:\\in\\lft_.txt");
            OutputStream os = new FileOutputStream("D:\\in\\out.txt");

            long start = System.currentTimeMillis();            
            testCase.pipe(is, os);
            long end = System.currentTimeMillis();

            System.out.println("Execution Time = " + (end - start) + " millis");
            System.out.println("============================================");

            is.close();
            os.close();
        }

    }

    private static PipeTestCase[] testCases = {

        new PipeTestCase("Fixed Buffer Read") {         
            @Override
            public void pipe(InputStream is, OutputStream os) throws IOException {
                byte[] buffer = new byte[1024];
                while(is.read(buffer) > -1) {
                    os.write(buffer);   
                }
            }
        },

        new PipeTestCase("dynamic Buffer Read") {           
            @Override
            public void pipe(InputStream is, OutputStream os) throws IOException {
                byte[] buffer = new byte[is.available()];
                while(is.read(buffer) > -1) {
                    os.write(buffer);   
                    buffer = new byte[is.available() + 1];
                }
            }
        },

        new PipeTestCase("Byte Read") {         
            @Override
            public void pipe(InputStream is, OutputStream os) throws IOException {
                int c; 
                while((c = is.read()) > -1) {
                    os.write(c);    
                }
            }
        }, 

        new PipeTestCase("NIO Read") {          
            @Override
            public void pipe(InputStream is, OutputStream os) throws IOException {
                FileChannel source      = ((FileInputStream) is).getChannel(); 
                FileChannel destnation  = ((FileOutputStream) os).getChannel();
                destnation.transferFrom(source, 0, source.size());
            }
        }, 

    };
}


abstract class PipeTestCase {
    private String approach; 
    public PipeTestCase( final String approach) {
        this.approach = approach;           
    }

    public String getApproach() {
        return approach;
    }

    public abstract void pipe(InputStream is, OutputStream os) throws IOException;
}

Sortie (fichier d'entrée ~ 4 Mo):

Fixed Buffer Read
Execution Time = 71 millis
============================================
dynamic Buffer Read
Execution Time = 167 millis
============================================
Byte Read
Execution Time = 29124 millis
============================================
NIO Read
Execution Time = 125 millis
============================================

'Dynamic Buffer Read' utilise la méthode available(). Mais ce n'est pas fiable selon Java docs

Il n'est jamais correct d'utiliser la valeur de retour de cette méthode pour allouer un tampon destiné à contenir toutes les données de ce flux.

"Byte Read" semble être très lent.

Donc, la lecture fixe du tampon est la meilleure option pour les tuyaux? Des pensées?

Je dirais qu'une taille de tampon fixe est la meilleure/la plus facile à comprendre. Cependant, il y a quelques problèmes.

  • Vous écrivez le tampon entier dans le flux de sortie à chaque fois. Pour le bloc final, la lecture peut avoir lu <1024 octets, vous devez donc en tenir compte lors de l'écriture (en gros, seulement écrire le nombre d'octets renvoyés par read()

  • Dans le cas du tampon dynamique, vous utilisez available(). Ce n'est pas un appel d'API extrêmement fiable. Je ne suis pas sûr dans ce cas à l'intérieur d'une boucle si ce sera correct, mais je ne serais pas surpris s'il était implémenté de manière sous-optimale dans certaines implémentations de InputStream.

  • Le dernier cas que vous castez vers FileInputStream. Si vous avez l'intention que cela soit à usage général, vous ne pouvez pas utiliser cette approche.

12
Mike Q

Je suis tombé sur cela, et la lecture finale peut causer des problèmes.

CHANGEMENT SUGGÉRÉ:

public void pipe(InputStream is, OutputStream os) throws IOException {
  int n;
  byte[] buffer = new byte[1024];
  while((n = is.read(buffer)) > -1) {
    os.write(buffer, 0, n);   // Don't allow any extra bytes to creep in, final write
  }
 os.close ();

Je suis également d'accord que 16384 est probablement une meilleure taille de tampon fixe que 1024.

A MON HUMBLE AVIS...

12
paulsm4

Pour les personnes à la recherche d'un one-liner , voici une solution de Apache commons :

IOUtils.copy(inputStream, outputStream);

Documentation ici . Il existe plusieurs méthodes copy avec différents paramètres. Il est également possible de spécifier la taille du tampon.

9
Dariusz

Java.io contient PipedInputStream et PipedOutputStream

PipedInputStream input = new PipedInputStream();
PipedOutputStream output = new PipedOutputStream (input);

écrire en entrée et il sera visible en sortie sous la forme d'un Outputstream. Les choses peuvent aussi fonctionner dans l'autre sens

7
Radu Simionescu