将InputStream传输到OutputStream的最佳方法

31

我正在尝试找到将InputStream传输到OutputStream的最佳方法。 我没有使用任何其他库,如Apache IO,的选项。 以下是代码片段和输出。

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

输出(~4MB输入文件):

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
============================================

'动态缓冲读取'使用available()方法。但根据Java文档,它不可靠。

从该方法返回值来分配一个旨在容纳此流中所有数据的缓冲区是不正确的。

'字节读取'似乎非常缓慢。

那么在管道中,“固定缓冲区读取”是最佳选项吗?有什么想法?


你的意思是 Apache IO 更好吗? - shareef
4个回答

21

Java 9

自从Java 9以后,一个人可以使用来自InputStream此方法

public long transferTo(OutputStream out) throws IOException

Java 9之前版本

Apache Commons一行代码:

IOUtils.copy(inputStream, outputStream);

这里有文档,有多个不同参数的copy方法,也可以指定缓冲区大小。


1
这并没有回答问题。问题说答案不能使用其他库。 - Nathan
2
你说得对。他没有回答问题。但是,这个答案给了我一个解决方案,因为我可以使用第三方工具。我会编辑答案以承认它不能满足他的需求,但对像我这样的其他人来说,这是一个有用的答案。所以谢谢。 - Perry Tew
源代码库明确地写在开头。谷歌对标题的索引最佳,因此这个问题的浏览量相当高。"其他库"备注没有被高度索引。 - Dariusz
1
它可能不能回答@OP的需求,但它回答了我的问题,我也不能很好地提出一个新问题,不是吗?它只会链接到这个问题,然后我会被告知不要问已经得到答案的问题。由于搜索引擎将我们发送到这里,因此有更多理由提供这些额外的答案:它们可以帮助那些不是OP,但几乎有相同问题的人。 - Haakon Løtveit

14
我遇到了这个问题,最后的读取可能会引起问题。
建议更改:
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 ();

我也同意16384比1024更好作为固定缓冲区大小。

在我看来...


1
缓冲区大小应该是一个参数,不同的使用情况需要不同的缓冲区大小。 - Mmmh mmh
@AurélienOoms 不,不是的。这个答案中的代码将适用于任何大于零的缓冲区大小。 - user207421

12

我认为固定缓冲区大小是最容易理解的。然而,存在一些问题。

  • 每次都要将整个缓冲区写入输出流。对于最后一个块,读取的字节数可能小于1024,因此在写入时需要考虑这一点(基本上只写入read()返回的字节数)。

  • 在动态缓冲区情况下,你使用了available()方法。这不是一个非常可靠的API调用。在循环内部,我不确定它是否可以正常工作,但如果实现InputStream的某些实现方式不够优秀,我不会感到惊讶。

  • 最后一种情况,你正在将其转换为 FileInputStream 。如果你打算使其通用,则不能使用此方法。


同意这三点。固定缓冲区的情况需要修复。动态缓冲区MHO是有问题的 - 如果出现available()在到达结尾之前返回0的情况(即从网络连接读取时),我不会感到惊讶。 - Axel
@Mike Q,目前NIO对我来说不是一个选择。该接口定义了InputStream和OutputStream。 - Siva Arunachalam
我通常使用固定的缓冲区大小为16k或更大,因为现在大多数人都有多余的16k内存 :) 此外,如果可能的话,最好知道要传入多少字节,这样您就不会过早结束写入。 - complistic

8

java.io 包含 PipedInputStreamPipedOutputStream

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

将内容写入input,它将作为Outputstream在output中显示。事情也可以反过来工作。


2
OP已经有了输入和输出 - 如何使用这些管道流连接两端? - OrangeDog
这个问题并不是关于管道的,而更多地是关于如何将一个文件复制到另一个文件。管道更多地是关于将输出流传递为另一个进程的输入流。 - Radu Simionescu
“write to input” - 你的意思是写入输出吗? - Llew Vallis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接