Підключення вхідного потоку до вихідного


76

оновлення в java9: https://docs.oracle.com/javase/9/docs/api/java/io/InputStream.html#transferTo-java.io.OutputStream-

Я бачив кілька подібних, але не зовсім те, що мені потрібно.

У мене є сервер, який в основному буде приймати вхідні дані від клієнта, клієнта A, і пересилати його, байт за байтом, іншому клієнту, клієнту B.

Я хотів би пов’язати свій вхідний потік клієнта A з моїм вихідним потоком клієнта B. Чи можливо це? Які способи це зробити?

Крім того, ці клієнти надсилають один одному повідомлення, які дещо залежать від часу, тому буферизація не спрацює. Я не хочу буфер, скажімо, 500, і клієнт надсилає 499 байт, а потім мій сервер затримує переадресацію 500 байт, оскільки він не отримав останній байт для заповнення буфера.

Зараз я аналізую кожне повідомлення, щоб знайти його довжину, потім читаю байти довжини, потім пересилаю їх. Я припустив (і перевірив), що це було б краще, ніж читання байта і пересилання байта знову і знову, оскільки це було б дуже повільно. Я також не хотів використовувати буфер або таймер з тієї причини, яку зазначив у своєму останньому абзаці - я не хочу, щоб повідомлення, які чекають дуже довго, просто через те, що буфер не заповнений.

Який хороший спосіб це зробити?

Відповіді:


86

Те, що ви використовуєте буфер, не означає, що потік повинен заповнювати цей буфер. Іншими словами, це повинно бути нормально:

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);
    }
}

Це повинно працювати нормально - в основному readвиклик блокуватиме , поки є деякі дані доступні, але він не буде чекати , поки це не все доступно для заповнення буфера. (Я вважаю, що це могло, і я вірюFileInputStream , як правило , буде заповнити буфер, але потік приєднаний до розетки, швидше за все , щоб дати вам дані відразу.)

Я думаю, що варто спочатку спробувати це просте рішення.


Так, я думаю, це все прояснює. Я думаю, мене плутали з readFully (), який вимагає заповнення буфера.
jbu

1
Я спробував ваш код, а також спробував прочитати повідомлення за повідомленням, прочитавши довжину повідомлення, потім зробивши байт [] buf = length; inputstream.read (buf) .... останній метод був швидшим, і я не впевнений, чому. Здається, виконується більше рядків коду, але це швидше. Майже вдвічі швидше.
jbu

2
@Zibbobz: Буде працювати будь-який розмір масиву - чим він більший, тим менше читань буде потрібно, але чим більше пам'яті потрібно, поки він працює. Це не так, як це має бути фактична довжина потоку.
Джон Скіт,

1
@sgibly: Ну, враховуючи, що close()воля все одно змиє , я не думаю, що це варто того, особисто. Звичайно, якщо ви берете подібний код, ви можете сміливо додавати його :)
Джон Скіт,

1
@sgibly: Я б сказав, що це погано задокументовано, а не намір полягає в тому, що кожен повинен викликати флеш ...
Джон Скіт,

77

Як щодо просто використання

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

і закінчити з цим?

з бібліотеки вводу-виводу jakarta apache commons, яка вже використовується величезною кількістю проектів, тому, ймовірно, ви вже маєте банку у своєму шляху до класу.


23
або просто скористатися самою функцією, оскільки виклик іншої функції з точно такими ж параметрами не потрібен ....
розробник android

так, це те, що я роблю особисто. Напевно, я набрав лише зайве ім’я методу як документацію, але воно не потрібне.
Дін Хіллер

1
З того, що я можу сказати, цей метод блокує, поки не буде пройдено введення. Отже, це слід робити в асинхронному ланцюжку для запитувача запитань.
Йона



10

Ви можете використовувати круговий буфер:

Код

// 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());


Залежність Мейвена

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


Деталі режиму

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


8

Асинхронний спосіб його досягнення.

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();
}

Це для передачі даних з одного потоку в інший без блокування поточного потоку.
Даніель Де Леон

3

BUFFER_SIZE - це розмір патронів для читання. Має бути> 1 кб і <10 Мб.

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
Має бути набагато менше 10 Мб. Це мова про TCP. Будь-який розмір, більший за буфер прийому сокета, абсолютно безглуздий, і вони вимірюються в кілобайтах, а не в мегабайтах.
user207421

2

Використовуйте org.apache.commons.io.IOUtils

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

або copyLarge для розміру> 2 Гб


1

Це версія Scala, яка є чистою та швидкою (без використання 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()
    }
  }

Це дозволяє використовувати >символ, наприклад, inputstream > outputstreamа також передавати спеціальні буфери / розміри.


Не могли б ви надати подібну реалізацію Java?
Luchostein

1
@Luchostein: Я відповідав на баггі-відповідь Скали Джорджа Плігора нижче
pathikrit

-16

Якщо ви користуєтесь функціоналом, це функція, написана в Scala, яка показує, як можна скопіювати вхідний потік у вихідний потік, використовуючи лише vals (а не vars).

def copyInputToOutputFunctional(inputStream: InputStream, outputStream: OutputStream,bufferSize: Int) {
  val buffer = new Array[Byte](bufferSize);
  def recurse() {
    val len = inputStream.read(buffer);
    if (len > 0) {
      outputStream.write(buffer.take(len));
      recurse();
    }
  }
  recurse();
}

Зверніть увагу, що це не рекомендується використовувати в програмі Java з малою доступною пам’яттю, оскільки за допомогою рекурсивної функції ви можете легко отримати помилку винятків переповнення стека


12
-1: Як рекурсивне рішення Scala відповідає питанню Java?
tomlogic

2
Метод рекурсії - рекурсивний хвіст. Якщо ви анотуєте його @tailrec, у вас не буде проблем із переповненням стека.
Сімао Мартінс

1
Ця відповідь підтверджує, що всі чисті кодери Java страждають від тиску своїх босів і потребують серйозного управління гнівом!
Джордж Плігоропулос
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.