web-dev-qa-db-ja.com

InputStreamをOutputStreamにパイプする最良の方法

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 docs

このメソッドの戻り値を使用して、このストリーム内のすべてのデータを保持するためのバッファを割り当てることは決して正しくありません。

「バイト読み取り」は非常に遅いようです。

パイプには「固定バッファ読み取り」が最適なオプションですか?何かご意見は?

固定バッファサイズが最も理解しやすい/最も簡単だと思います。ただし、いくつかの問題があります。

  • 毎回バッファ全体を出力ストリームに書き込みます。最後のブロックでは、読み取りが1024バイト未満になる可能性があるため、書き込みを行うときにこれを考慮する必要があります(基本的に、read()によって返されるバイト数のみを書き込みます)

  • 動的バッファの場合、available()を使用します。これはひどく信頼できるAPI呼び出しではありません。この場合、ループ内で大丈夫かどうかはわかりませんが、InputStreamの一部の実装で最適に実装されていない場合は驚かないでしょう。

  • 最後のケースはFileInputStreamにキャストしています。これを一般的な目的にする場合は、このアプローチを使用できません。

12
Mike Q

私はこれに出くわし、最終的な読み取りが問題を引き起こす可能性があります。

推奨される変更:

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よりも固定バッファサイズの方が優れている可能性が高いことに同意します。

私見では...

12
paulsm4

one-linerを探している人のために、 Apache commons からの解決策があります:

IOUtils.copy(inputStream, outputStream);

ドキュメントはこちら 。異なるパラメーターを持つ複数のcopyメソッドがあります。バッファサイズを指定することもできます。

9
Dariusz

Java.ioにはPipedInputStreamPipedOutputStreamが含まれます

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

入力に書き込むと、出力でOutputstreamとして表示されます。物事は他の方法でも回避できます

7
Radu Simionescu