Kết nối luồng đầu vào với luồng đầu ra


76

cập nhật trong java9: https://docs.oracle.com/javase/9/docs/api/java/io/InputStream.html#transferTo-java.io.OutputStream-

Tôi đã thấy một số chủ đề tương tự, nhưng không-hoàn-toàn-điều-tôi cần.

Tôi có một máy chủ, về cơ bản sẽ nhận đầu vào từ một máy khách, máy khách A và chuyển tiếp nó, từng byte cho từng byte, tới một máy khách khác, máy khách B.

Tôi muốn kết nối luồng đầu vào của máy khách A với luồng đầu ra của máy khách B. Điều đó có thể thực hiện được không? Những cách để làm điều đó là gì?

Ngoài ra, các ứng dụng khách này đang gửi cho nhau các tin nhắn, hơi nhạy cảm về thời gian, vì vậy việc lưu vào bộ đệm sẽ không thực hiện được. Tôi không muốn một bộ đệm nói là 500 và một máy khách gửi 499 byte và sau đó máy chủ của tôi ngừng chuyển tiếp 500 byte vì nó không nhận được byte cuối cùng để lấp đầy bộ đệm.

Ngay bây giờ, tôi đang phân tích cú pháp từng tin nhắn để tìm độ dài của nó, sau đó đọc các byte độ dài, rồi chuyển tiếp chúng. Tôi đã hình dung (và đã thử nghiệm) điều này sẽ tốt hơn việc đọc một byte và chuyển tiếp một byte nhiều lần vì điều đó sẽ rất chậm. Tôi cũng không muốn sử dụng bộ đệm hoặc bộ đếm thời gian vì lý do tôi đã nêu trong đoạn cuối của mình - tôi không muốn các tin nhắn phải đợi một thời gian dài mới được thông qua đơn giản vì bộ đệm chưa đầy.

Cách tốt để làm điều này là gì?

Câu trả lời:


86

Chỉ vì bạn sử dụng bộ đệm không có nghĩa là luồng phải lấp đầy bộ đệm đó. Nói cách khác, điều này sẽ ổn:

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

Điều đó sẽ hoạt động tốt - về cơ bản readcuộc gọi sẽ chặn cho đến khi có sẵn một số dữ liệu, nhưng sẽ không đợi cho đến khi tất cả có sẵn để lấp đầy bộ đệm. (Tôi cho rằng nó có thể và tôi tin rằng FileInputStreamthường sẽ lấp đầy bộ đệm, nhưng một luồng được gắn vào một ổ cắm có nhiều khả năng cung cấp cho bạn dữ liệu ngay lập tức.)

Tôi nghĩ ít nhất cũng nên thử giải pháp đơn giản này trước.


Vâng, tôi nghĩ điều này làm sáng tỏ mọi thứ. Tôi nghĩ rằng tôi đã nhầm lẫn với readFully () yêu cầu bộ đệm để lấp đầy.
jbu

1
Tôi đã thử mã của bạn và tôi cũng đã thử đọc từng tin nhắn bằng cách đọc độ dài của tin nhắn sau đó thực hiện một byte [] buf = length; inputstream.read (buf) .... phương pháp thứ hai nhanh hơn và tôi không chắc tại sao. Nó dường như thực thi nhiều dòng mã hơn nhưng nó nhanh hơn. Nhanh gấp gần 2 lần.
jbu

2
@Zibbobz: Bất kỳ kích thước mảng nào cũng được - kích thước mảng càng lớn thì càng cần ít lần đọc hơn, nhưng càng tốn nhiều bộ nhớ trong khi hoạt động. Nó không giống như độ dài thực của luồng.
Jon Skeet

1
@sgibly: close()Dù sao thì a cũng sẽ tuôn ra, cá nhân tôi nghĩ nó không đáng. Tất nhiên, nếu bạn lấy mã như thế này bạn nên cảm thấy rất tự do để thêm nó :)
Jon Skeet

1
@sgibly: Tôi muốn nói rằng nó kém tài liệu chứ không phải là ý định được rằng mọi người đều có để tuôn ra cuộc gọi ...
Jon Skeet

77

Làm thế nào về chỉ sử dụng

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

và được thực hiện với nó?

từ thư viện jakarta apache commons i / o được sử dụng bởi một lượng lớn các dự án nên có thể bạn đã có jar trong classpath của mình rồi.


23
hoặc chỉ sử dụng chính hàm đó, vì không cần gọi một hàm khác có cùng thông số chính xác ....
nhà phát triển android

vâng, đó là những gì cá nhân tôi làm. Tôi đoán tôi chỉ nhập tên phương thức bổ sung làm tài liệu nhưng nó không cần thiết.
Dean Hiller

1
Từ những gì tôi có thể biết, phương pháp này đang chặn cho đến khi đầu vào while được chuyển qua. Do đó, điều này nên được thực hiện trong một chuỗi không đồng bộ cho người hỏi câu hỏi.
Jonah



10

Bạn có thể sử dụng một bộ đệm tròn:

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


Maven phụ thuộc

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


Chi tiết chế độ

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


8

Cách không đồng bộ để đạt được nó.

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

Điều này là để chuyển dữ liệu từ luồng này sang luồng khác mà không chặn luồng hiện tại của bạn.
Daniel De León

3

BUFFER_SIZE là kích thước của chucks để đọc. Phải> 1kb và <10MB.

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
Nên ít hơn rất nhiều 10MB. Đây là TCP mà chúng ta đang nói đến. Mọi kích thước lớn hơn bộ đệm nhận socket đều hoàn toàn vô nghĩa và chúng được đo bằng kilobyte, không phải megabyte.
user207421

2

Sử dụng org.apache.commons.io.IOUtils

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

hoặc copyLarge cho kích thước> 2GB


1

Đây là phiên bản Scala sạch sẽ và nhanh chóng (không có 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()
    }
  }

Điều này cho phép sử dụng >biểu tượng ví dụ inputstream > outputstreamvà cũng có thể chuyển vào bộ đệm / kích thước tùy chỉnh.


Bạn có thể cung cấp một số triển khai Java tương tự không?
Luchostein

1
@Luchostein: Tôi đã trả lời câu trả lời Scala đầy lỗi của George Pligor bên dưới
pathikrit

-16

Trong trường hợp bạn đang sử dụng chức năng, đây là một hàm được viết bằng Scala cho thấy cách bạn có thể sao chép luồng đầu vào sang luồng đầu ra chỉ sử dụng vals (chứ không phải 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();
}

Lưu ý rằng điều này không được khuyến khích sử dụng trong ứng dụng java có ít bộ nhớ vì với hàm đệ quy, bạn có thể dễ dàng gặp lỗi ngoại lệ tràn ngăn xếp


12
-1: Giải pháp Scala đệ quy phù hợp với câu hỏi Java như thế nào?
tomlogic

2
Phương thức đệ quy là đệ quy đuôi. Nếu bạn chú thích nó bằng @tailrec, bạn sẽ không gặp sự cố tràn ngăn xếp.
Simão Martins

1
Câu trả lời này xác minh rằng tất cả các lập trình viên java thuần túy đang phải chịu áp lực từ các ông chủ của họ và cần quản lý cơn giận dữ một cách nghiêm túc!
George Pligoropoulos
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.