web-dev-qa-db-fra.com

Connecter un flux d'entrée à un flux de sortie

mise à jour en Java9: https://docs.Oracle.com/javase/9/docs/api/Java/io/InputStream.html#transferTo-Java.io.OutputStream-

J'ai vu des discussions similaires, mais pas tout à fait ce qu'il me faut.

J'ai un serveur, qui prend en gros les entrées d'un client, le client A, et les transmet, octet pour octet, à un autre client, le client B.

J'aimerais connecter mon flux d'entrée du client A avec mon flux de sortie du client B. Est-ce possible? Quels sont les moyens de le faire?

En outre, ces clients s'envoient des messages, qui sont quelque peu sensibles au temps, la mise en mémoire tampon ne fonctionnera donc pas. Je ne veux pas d'un tampon de 500, et un client envoie 499 octets. Mon serveur s'abstient de transférer les 500 octets car il n'a pas reçu le dernier octet pour remplir le tampon.

En ce moment, j'analyse chaque message pour trouver sa longueur, puis je lis des octets de longueur, puis je les transfère. J'ai pensé (et testé) que ce serait mieux que de lire un octet et de le transmettre encore et encore car cela serait très lent. Je ne voulais pas non plus utiliser un tampon ou une minuterie pour les raisons que j'ai mentionnées dans mon dernier paragraphe - je ne veux pas que les messages attendent très longtemps, simplement parce que le tampon n'est pas plein.

Quel est un bon moyen de faire cela?

72
jbu

Ce n’est pas parce que vous utilisez un tampon que le flux doit remplir ce tampon. En d'autres termes, ça devrait aller:

public static void copyStream(InputStream input, OutputStream output)
    throws IOException
{
    byte[] buffer = new byte[1024]; // Adjust if you want
    int bytesRead;
    while ((bytesRead = input.read(buffer)) != -1)
    {
        output.write(buffer, 0, bytesRead);
    }
}

Cela devrait fonctionner correctement - essentiellement, l'appel read bloquera jusqu'à ce qu'il y ait certaines données disponibles, mais il n'attendra pas qu'il soit tout disponible pour remplir le tampon . (Je suppose que c'est possible, et je crois que FileInputStream habituellement va remplir le tampon, mais un flux attaché à un socket est plus susceptible de vous donner les données immédiatement.)

Je pense que cela vaut au moins d'essayer d'abord cette solution simple.

84
Jon Skeet

Pourquoi ne pas utiliser

void feedInputToOutput(InputStream in, OutputStream out) {
   IOUtils.copy(in, out);
}

et en finir avec ça?

de la bibliothèque i/o de jakarta Apache commons qui est déjà utilisée par un grand nombre de projets, vous avez donc probablement déjà le fichier jar dans votre classpath.

74
Dean Hiller

Pour compléter, goyave possède également un tilitaire pratique pour cette

ByteStreams.copy(input, output);
21
JesusFreke

JDK 9 a ajouté InputStream#transferTo(OutputStream out) pour cette fonctionnalité.

12
Warren M. Nocos

Vous pouvez utiliser un tampon circulaire:

Code

// buffer all data in a circular buffer of infinite size
CircularByteBuffer cbb = new CircularByteBuffer(CircularByteBuffer.INFINITE_SIZE);
class1.putDataOnOutputStream(cbb.getOutputStream());
class2.processDataFromInputStream(cbb.getInputStream());


Dépendance Maven

<dependency>
    <groupId>org.ostermiller</groupId>
    <artifactId>utils</artifactId>
    <version>1.07.00</version>
</dependency>


Détails du mode

http://ostermiller.org/utils/CircularBuffer.html

9
Stephan

Manière asynchrone d'y parvenir.

void inputStreamToOutputStream(final InputStream inputStream, final OutputStream out) {
    Thread t = new Thread(new Runnable() {

        public void run() {
            try {
                int d;
                while ((d = inputStream.read()) != -1) {
                    out.write(d);
                }
            } catch (IOException ex) {
                //TODO make a callback on exception.
            }
        }
    });
    t.setDaemon(true);
    t.start();
}
6
Daniel De León

BUFFER_SIZE est la taille des mandrins à lire. Doit être> 1 Ko et <10 Mo.

private static final int BUFFER_SIZE = 2 * 1024 * 1024;
private void copy(InputStream input, OutputStream output) throws IOException {
    try {
        byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead = input.read(buffer);
        while (bytesRead != -1) {
            output.write(buffer, 0, bytesRead);
            bytesRead = input.read(buffer);
        }
    //If needed, close streams.
    } finally {
        input.close();
        output.close();
    }
}
2
Leandro Latorre

Utilisez org.Apache.commons.io.IOUtils

InputStream inStream = new ...
OutputStream outStream = new ...
IOUtils.copy(inStream, outStream);

ou copyLarge pour la taille> 2 Go

1
Adam111p

Ceci est une version Scala propre et rapide (pas de stackoverflow)):

  import scala.annotation.tailrec
  import Java.io._

  implicit class InputStreamOps(in: InputStream) {
    def >(out: OutputStream): Unit = pipeTo(out)

    def pipeTo(out: OutputStream, bufferSize: Int = 1<<10): Unit = pipeTo(out, Array.ofDim[Byte](bufferSize))

    @tailrec final def pipeTo(out: OutputStream, buffer: Array[Byte]): Unit = in.read(buffer) match {
      case n if n > 0 =>
        out.write(buffer, 0, n)
        pipeTo(out, buffer)
      case _ =>
        in.close()
        out.close()
    }
  }

Cela permet d'utiliser > symbole, par exemple inputstream > outputstream et passe également dans les tampons/tailles personnalisés.

0
pathikrit