Java의 파일 복사 기록

15704 단어 NIO2Commons-IOnioJava
전통적인 Java로 작성된 시스템 코드를 보면 다음과 같이 InputStream으로 파일을 열고 OutputStream으로 복제 대상 파일을 쓰는 경우가 있다.
try(InputStream input = new FileInputStream(srcFile);
    OutputStream output = new FileOutputStream(dstFile)) {
    byte[] buffer = new byte[BUFFER_SIZE];
    int size = -1;
    while ((size = input.read(buffer)) > 0) {
      output.write(buffer, 0, size);
    }
}
다른 방법이 뭐가 있겠습니까?파일 복사의 역사를 돌이켜보면commons-io의 실시 변천은 각자의 기법의 기준을 얻으려고 한다.

1. 메모리에 모든 버퍼


2002년commons-io의FileUtils에서 처음으로 파일 복제 방법이 나타났을 때, 지금은 이미 얄미운 코드가 되었다.
FileUtils.java
public static void fileCopy(String inFileName, String outFileName) throws
    Exception
{
    String content = FileUtils.fileRead(inFileName);
    FileUtils.fileWrite(outFileName, content);
}
지금 이런 코드를 쓰면 밀려나고...

2. InputStream / OutputStream


상술한 방법이 좀 지나치기 때문에 2003년 FileInputStream에서 파일 내용을 읽고 FileOutputStream으로 써서 당시의 정통 설치가 되었다.그럼에도 불구하고 J2SE1.4는 2002년에 발행된 것으로 후술한 FileChannel이 사용할 수 있는 시대다.
https://github.com/apache/commons-io/blob/d9d353082503a217fa6c6510622973d018db0e26/src/java/org/apache/commons/io/FileUtils.java#L604
https://github.com/apache/commons-io/blob/d9d353082503a217fa6c6510622973d018db0e26/src/java/org/apache/commons/io/CopyUtils.java
CopyUtils.java
final byte[] buffer = new byte[bufferSize];
int count = 0;
int n = 0;
while (-1 != (n = input.read(buffer))) {
    output.write(buffer, 0, n);
    count += n;
}

3. FileChannel (NIO)


FileChannel 은 2008 년에 드디어 사용되기 시작했습니다.
FileUtils.java
private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException {
    if (destFile.exists() && destFile.isDirectory()) {
        throw new IOException("Destination '" + destFile + "' exists but is a directory");
    }

    FileChannel input = new FileInputStream(srcFile).getChannel();
    try {
        FileChannel output = new FileOutputStream(destFile).getChannel();
        try {
            output.transferFrom(input, 0, input.size());
        } finally {
            IOUtils.closeQuietly(output);
        }
     } finally {
        IOUtils.closeQuietly(input);
     }

    if (srcFile.length() != destFile.length()) {
        throw new IOException("Failed to copy full contents from '" +
            srcFile + "' to '" + destFile + "'");
    }
    if (preserveFileDate) {
        destFile.setLastModified(srcFile.lastModified());
    }
}
commons-io의 현재 트렁크 구현도 이렇지만 윈도큰 파일을 복사할 수 없음(IO-175)에 있기 때문에 30MB마다 트렁크 프롬을 순환한다.

4. Files (NIO2)


자바 7에서 NIO2로, 자바로.nio.file.Files 에 copy 메서드가 있습니다.따라서 (파일 복사에 관한)과commons-io는 작별인사를 하고 Files의copy를 직접 사용하면 같은 효과를 기대할 수 있다.
Files.copy(source.toPath(), dest, StandardCopyOption.REPLACE_EXISTING);

기준


그렇다면 이 기준들을 얻어보자.기본 코드는 다음과 같습니다.20MB 파일을 중복 복제할 때의 처리량을 비교합니다.

Linux


디스크 성능의 기계에서
% sync; time bash -c "(dd if=/dev/zero of=bf bs=8k count=500000; sync)" 
4096000000 バイト (4.1 GB) コピーされました、 7.00345 秒、 585 MB/秒
결과는 다음과 같다.
Benchmark                            Mode  Cnt  Score   Error  Units
InputStream2OutputStream.benchmark  thrpt    5  1.669 ± 0.055  ops/s
NIOFileChannel.benchmark            thrpt    5  1.860 ± 0.113  ops/s
NIO2Files.benchmark                 thrpt    5  7.110 ± 1.115  ops/s
Files 를 사용한 복제 방식은 압도적인 빠른 결과입니다.

Files가 빠른 이유


Files를 거슬러 올라가는 복제 방법sun.nio.fs.UnixCopyFile은 실제 복제된 것 같다.
    private static void copyFile(UnixPath source,
                                 UnixFileAttributes attrs,
                                 UnixPath  target,
                                 Flags flags,
                                 long addressToPollForCancel)
        throws IOException
    {
        int fi = -1;
        try {
            fi = open(source, O_RDONLY, 0);
        } catch (UnixException x) {
            x.rethrowAsIOException(source);
        }

        try {
            // open new file
            int fo = -1;
            try {
                fo = open(target,
                           (O_WRONLY |
                            O_CREAT |
                            O_EXCL),
                           attrs.mode());
            } catch (UnixException x) {
                x.rethrowAsIOException(target);
            }


            // set to true when file and attributes copied
            boolean complete = false;
            try {
                // transfer bytes to target file
                try {
                    transfer(fo, fi, addressToPollForCancel);
NIO 모드와 마찬가지로 파일을 열어 버퍼 복사를 하지만 오픈으로 거슬러 올라가면sun입니다.misc.Unsafe를 사용하여 로컬 버퍼를 사용합니다.그래서 이렇게 차이가 나는 거야.

Windows


Windows로 같은 기준을 재생해 보았습니다.
Benchmark                            Mode  Cnt  Score   Error  Units
InputStream2OutputStream.benchmark  thrpt    5  1.032 ± 0.421  ops/s
NIOFileChannel.benchmark            thrpt    5  5.466 ± 9.434  ops/s
NIO2Files.benchmark                 thrpt    5  3.153 ± 2.775  ops/s
FileChannel 및 Filescopy의 결과가 역전되다.Windows에 익숙하지 않아서 이유를 잘 모르겠는데...
(Files.copy에서 사용CopyFileExW API, 오버헤드입니까?

총결산


java.nio.파일의 반은 자바입니다.IO와의 호환성은 약하지만, 네이티브라고 많이 부르는 기능 때문에 성능 면에서 이점이 크다.
특히 거대한 서류와 많은 서류를 처리할 때 적극적으로 사용하는 게 좋지 않겠나.
  • Java1.4 이상 FileChannel을 사용합니다.
  • Java7 이상은 Files입니다.코피를 사용하세요.
  • 좋은 웹페이지 즐겨찾기