将输入流连接到输出流


76

在Java9中更新: https

我看到了一些类似但不是很需要的线程。

我有一台服务器,它基本上将从客户端A的客户端接收输入,并将其逐字节转发到另一个客户端B。

我想将客户端A的输入流与客户端B的输出流连接。这可能吗?有什么方法可以做到这一点?

而且,这些客户端正在互相发送消息,这在某种程度上是时间敏感的,因此不会进行缓冲。我不希望有500个缓冲区,而客户端发送499个字节,然后我的服务器推迟转发500个字节,因为它没有收到填充缓冲区的最后一个字节。

现在,我正在解析每个消息以找到其长度,然后读取长度字节,然后转发它们。我认为(并测试)这比读取字节并一次又一次地转发字节要好,因为那样会很慢。由于我在上一段中提到的原因,我也不想使用缓冲区或计时器-我不想仅仅因为缓冲区未满而等待很长时间的消息才能通过。

有什么好方法吗?

Answers:


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
我已经尝试了您的代码,还尝试通过读取消息的长度然后逐个读取字节来读取消息,然后执行byte [] buf = length; inputstream.read(buf)....后一种方法更快,但我不确定为什么。它似乎执行更多的代码行,但速度更快。快了将近2倍。
jbu

2
@Zibbobz:任何数组大小都可以使用-更大的数组,需要的读取次数更少,但是在工作时占用的内存更多。并非必须是流的实际长度。
乔恩·斯基特

1
@sgfully:好吧,close()无论如何a都会冲洗它,我个人认为不值得。当然,如果您采用这样的代码,则可以随意添加它:)
Jon Skeet 2014年

1
@sgfully:我想说的是记录薄薄,而不是每个人都必须打同花顺的意图……
Jon Skeet 2014年

77

只是使用怎么样

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

并完成它?

来自jakarta apache commons i / o库,该库已经被大量项目使用,因此您可能已经在类路径中包含了jar。


23
或者只是使用功能本身,因为调用具有完全相同的参数,另一个功能是不需要....
Android开发者

是的,那是我个人所做的。我猜我只输入了额外的方法名称作为文档,但这不是必需的。
Dean Hiller 2014年

1
据我所知,该方法一直阻塞,直到通过while输入。因此,这应该在问题发问者的异步线程中完成。
乔纳2015年



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


Maven依赖

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

这是在不阻塞当前线程的情况下将数据从一个流传输到另一个流。
Daniel DeLeón15年

3

BUFFER_SIZE是要读取的卡盘的大小。应大于1kb,小于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
应该少于10MB。我们正在谈论的是TCP。任何大于套接字接收缓冲区的大小都是完全没有意义的,它们以千字节为单位,而不是以兆字节为单位。
罗恩侯爵

2

使用org.apache.commons.io.IOUtils

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

copyLarge for size> 2GB


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),2016年

1
@Luchostein:我在下面回应由George Pligor提出的有问题的Scala答案
pathikrit

-16

如果您要使用函数,则此函数是用Scala编写的函数,该函数显示如何仅使用val(而不使用var)将输入流复制到输出流。

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程序员都在遭受老板的压力,并且需要认真的愤怒管理!
George Pligoropoulos'3
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.