Java에서 파일을 복사하는 표준 간결한 방법은 무엇입니까?

StackOverflow https://stackoverflow.com/questions/106770

  •  01-07-2019
  •  | 
  •  

문제

Java에서 파일을 복사하는 유일한 방법은 스트림을 열고, 버퍼를 선언하고, 한 파일을 읽고, 반복하고, 다른 Steam에 쓰는 것뿐이라는 사실이 항상 저를 괴롭혔습니다.웹에는 이러한 유형의 솔루션에 대한 유사하면서도 약간 다른 구현이 많이 있습니다.

Java 언어 범위 내에서 유지되는 더 좋은 방법이 있습니까(즉, OS 특정 명령 실행이 포함되지 않음을 의미)?아마도 신뢰할 수 있는 오픈 소스 유틸리티 패키지에서 최소한 이러한 기본 구현을 모호하게 하고 한 줄 솔루션을 제공할 수 있을까요?

도움이 되었습니까?

해결책

툴킷이 위에서 언급했듯이 Apache Commons IO가 특히 좋습니다. FileUtils.복사파일();그것은 당신을 위해 모든 무거운 짐을 처리합니다.

그리고 포스트스크립트로 최근 버전의 FileUtils(예: 2.0.1 릴리스)에는 파일 복사를 위해 NIO 사용이 추가되었습니다. NIO는 파일 복사 성능을 크게 향상시킬 수 있습니다, 이는 NIO 루틴이 Java 계층을 통해 바이트를 읽고 쓰는 방식으로 처리하는 대신 OS/파일 시스템에 직접 복사하는 것을 연기하기 때문입니다.따라서 성능을 찾고 있다면 최신 버전의 FileUtils를 사용하고 있는지 확인하는 것이 좋습니다.

다른 팁

나는 Apache Commons와 같은 메가 API를 사용하지 않을 것입니다.이는 단순한 작업이며 새 NIO 패키지의 JDK에 내장되어 있습니다.이전 답변에서 이미 연결되어 있었지만 NIO API의 핵심 방법은 새로운 기능 "transferTo" 및 "transferFrom"입니다.

http://java.sun.com/javase/6/docs/api/java/nio/channels/FileChannel.html#transferTo(long,%20long,%20java.nio.channels.WritableByteChannel)

링크된 기사 중 하나는 transferFrom을 사용하여 이 기능을 코드에 통합하는 방법에 대한 좋은 방법을 보여줍니다.

public static void copyFile(File sourceFile, File destFile) throws IOException {
    if(!destFile.exists()) {
        destFile.createNewFile();
    }

    FileChannel source = null;
    FileChannel destination = null;

    try {
        source = new FileInputStream(sourceFile).getChannel();
        destination = new FileOutputStream(destFile).getChannel();
        destination.transferFrom(source, 0, source.size());
    }
    finally {
        if(source != null) {
            source.close();
        }
        if(destination != null) {
            destination.close();
        }
    }
}

NIO를 배우는 것은 약간 까다로울 수 있으므로 밤새 NIO를 배우기 전에 이 메커니즘을 신뢰하는 것이 좋습니다.개인적인 경험으로 볼 때 경험이 없고 java.io 스트림을 통해 IO를 접했다면 배우기가 매우 어려울 수 있습니다.

이제 Java 7에서는 다음과 같은 try-with-resource 구문을 사용할 수 있습니다.

public static void copyFile( File from, File to ) throws IOException {

    if ( !to.exists() ) { to.createNewFile(); }

    try (
        FileChannel in = new FileInputStream( from ).getChannel();
        FileChannel out = new FileOutputStream( to ).getChannel() ) {

        out.transferFrom( in, 0, in.size() );
    }
}

또는 Java 7에 도입된 새로운 Files 클래스를 사용하여 이 작업을 수행할 수도 있습니다.

public static void copyFile( File from, File to ) throws IOException {
    Files.copy( from.toPath(), to.toPath() );
}

꽤 멋지죠?

  • 이러한 방법은 성능을 고려하여 설계되었습니다(운영 체제 기본 I/O와 통합됨).
  • 이러한 방법은 파일, 디렉터리 및 링크에서 작동합니다.
  • 제공된 각 옵션은 생략될 수 있습니다. 이는 선택 사항입니다.

유틸리티 클래스

package com.yourcompany.nio;

class Files {

    static int copyRecursive(Path source, Path target, boolean prompt, CopyOptions options...) {
        CopyVisitor copyVisitor = new CopyVisitor(source, target, options).copy();
        EnumSet<FileVisitOption> fileVisitOpts;
        if (Arrays.toList(options).contains(java.nio.file.LinkOption.NOFOLLOW_LINKS) {
            fileVisitOpts = EnumSet.noneOf(FileVisitOption.class) 
        } else {
            fileVisitOpts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
        }
        Files.walkFileTree(source[i], fileVisitOpts, Integer.MAX_VALUE, copyVisitor);
    }

    private class CopyVisitor implements FileVisitor<Path>  {
        final Path source;
        final Path target;
        final CopyOptions[] options;

        CopyVisitor(Path source, Path target, CopyOptions options...) {
             this.source = source;  this.target = target;  this.options = options;
        };

        @Override
        FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
        // before visiting entries in a directory we copy the directory
        // (okay if directory already exists).
        Path newdir = target.resolve(source.relativize(dir));
        try {
            Files.copy(dir, newdir, options);
        } catch (FileAlreadyExistsException x) {
            // ignore
        } catch (IOException x) {
            System.err.format("Unable to create: %s: %s%n", newdir, x);
            return SKIP_SUBTREE;
        }
        return CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        Path newfile= target.resolve(source.relativize(file));
        try {
            Files.copy(file, newfile, options);
        } catch (IOException x) {
            System.err.format("Unable to copy: %s: %s%n", source, x);
        }
        return CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
        // fix up modification time of directory when done
        if (exc == null && Arrays.toList(options).contains(COPY_ATTRIBUTES)) {
            Path newdir = target.resolve(source.relativize(dir));
            try {
                FileTime time = Files.getLastModifiedTime(dir);
                Files.setLastModifiedTime(newdir, time);
            } catch (IOException x) {
                System.err.format("Unable to copy all attributes to: %s: %s%n", newdir, x);
            }
        }
        return CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) {
        if (exc instanceof FileSystemLoopException) {
            System.err.println("cycle detected: " + file);
        } else {
            System.err.format("Unable to copy: %s: %s%n", file, exc);
        }
        return CONTINUE;
    }
}

디렉터리 또는 파일 복사

long bytes = java.nio.file.Files.copy( 
                 new java.io.File("<filepath1>").toPath(), 
                 new java.io.File("<filepath2>").toPath(),
                 java.nio.file.StandardCopyOption.REPLACE_EXISTING,
                 java.nio.file.StandardCopyOption.COPY_ATTRIBUTES,
                 java.nio.file.LinkOption.NOFOLLOW_LINKS);

디렉터리 또는 파일 이동

long bytes = java.nio.file.Files.move( 
                 new java.io.File("<filepath1>").toPath(), 
                 new java.io.File("<filepath2>").toPath(),
                 java.nio.file.StandardCopyOption.ATOMIC_MOVE,
                 java.nio.file.StandardCopyOption.REPLACE_EXISTING);

디렉토리나 파일을 재귀적으로 복사하기

long bytes = com.yourcompany.nio.Files.copyRecursive( 
                 new java.io.File("<filepath1>").toPath(), 
                 new java.io.File("<filepath2>").toPath(),
                 java.nio.file.StandardCopyOption.REPLACE_EXISTING,
                 java.nio.file.StandardCopyOption.COPY_ATTRIBUTES
                 java.nio.file.LinkOption.NOFOLLOW_LINKS );

Java 7에서는 쉽습니다 ...

File src = new File("original.txt");
File target = new File("copy.txt");

Files.copy(src.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);

파일을 복사하여 대상 경로에 저장하려면 아래 방법을 사용할 수 있습니다.

public void copy(File src, File dst) throws IOException {
    InputStream in = new FileInputStream(src);
    try {
        OutputStream out = new FileOutputStream(dst);
        try {
            // Transfer bytes from in to out
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
        } finally {
            out.close();
        }
    } finally {
        in.close();
    }
}

이러한 모든 메커니즘은 권한과 같은 메타데이터가 아닌 파일의 내용만 복사한다는 점에 유의하세요.따라서 Linux에서 실행 가능한 .sh 파일을 복사하거나 이동하는 경우 새 파일을 실행할 수 없습니다.

실제로 파일을 복사하거나 이동하려면, 즉 명령줄에서 복사하는 것과 동일한 결과를 얻으려면 실제로 기본 도구를 사용해야 합니다.셸 스크립트 또는 JNI입니다.

분명히 이것은 Java 7에서 수정되었을 수 있습니다. http://today.java.net/pub/a/today/2008/07/03/jsr-203-new-file-apis.html.손가락이 교차했습니다!

Google의 Guava 라이브러리에는 복사 방법:

public static void 복사(파일 from,
                        파일 to)
                 throws IO예외
한 파일의 모든 바이트를 다른 파일로 복사합니다.

경고: 만약에 to 기존 파일을 나타내면 해당 파일은 다음의 내용으로 덮어 씁니다. from.만약에 to 그리고 from 을 참조하다 같은 파일, 해당 파일의 내용이 삭제됩니다.

매개변수:from - 소스 파일to - 대상 파일

던지기: IO예외 - I/O 오류가 발생한 경우IllegalArgumentException - 만약에 from.equals(to)

Java 7에서 표준으로 사용 가능한 path.copyTo:http://openjdk.java.net/projects/nio/javadoc/java/nio/file/Path.html http://java.sun.com/docs/books/tutorial/essential/io/copy.html

파일 복사와 같이 일반적이고 간단한 것을 표준화하는 데 그렇게 오랜 시간이 걸렸다는 것을 믿을 수 없습니다.

위 코드에는 세 가지 가능한 문제가 있습니다.

  1. getChannel에서 예외가 발생하면 공개 스트림이 누출될 수 있습니다.
  2. 대용량 파일의 경우 OS가 처리할 수 있는 것보다 더 많은 파일을 한 번에 전송하려고 할 수 있습니다.
  3. transferFrom의 반환 값을 무시하고 있으므로 파일의 일부만 복사할 수 있습니다.

이는 이유 org.apache.tools.ant.util.ResourceUtils.copyResource 너무 복잡해요.또한 transferFrom은 정상이지만 Linux의 JDK 1.4에서는 transferTo가 중단됩니다(참조: 버그 ID:5056395) – 제시 글릭 얀

이미 Spring을 사용하는 웹 애플리케이션에 있고 간단한 파일 복사를 위해 Apache Commons IO를 포함하지 않으려면 다음을 사용할 수 있습니다. FileCopyUtils Spring 프레임워크의 일부입니다.

한 줄의 코드로 파일을 쉽게 복사할 수 있는 세 가지 방법이 있습니다!

자바7:

java.nio.file.Files#복사

private static void copyFileUsingJava7Files(File source, File dest) throws IOException {
    Files.copy(source.toPath(), dest.toPath());
}

아파치 커먼즈 IO:

FileUtils#copyFile

private static void copyFileUsingApacheCommonsIO(File source, File dest) throws IOException {
    FileUtils.copyFile(source, dest);
}

구아바 :

파일#복사

private static void copyFileUsingGuava(File source,File dest) throws IOException{
    Files.copy(source,dest);          
}
public static void copyFile(File src, File dst) throws IOException
{
    long p = 0, dp, size;
    FileChannel in = null, out = null;

    try
    {
        if (!dst.exists()) dst.createNewFile();

        in = new FileInputStream(src).getChannel();
        out = new FileOutputStream(dst).getChannel();
        size = in.size();

        while ((dp = out.transferFrom(in, p, size)) > 0)
        {
            p += dp;
        }
    }
    finally {
        try
        {
            if (out != null) out.close();
        }
        finally {
            if (in != null) in.close();
        }
    }
}

내 테스트에 따르면 버퍼를 사용한 NIO 복사가 가장 빠릅니다.내 테스트 프로젝트의 아래 작업 코드를 참조하십시오. https://github.com/mhisoft/fastcopy

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.DecimalFormat;


public class test {

private static final int BUFFER = 4096*16;
static final DecimalFormat df = new DecimalFormat("#,###.##");
public static void nioBufferCopy(final File source, final File target )  {
    FileChannel in = null;
    FileChannel out = null;
    double  size=0;
    long overallT1 =  System.currentTimeMillis();

    try {
        in = new FileInputStream(source).getChannel();
        out = new FileOutputStream(target).getChannel();
        size = in.size();
        double size2InKB = size / 1024 ;
        ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER);

        while (in.read(buffer) != -1) {
            buffer.flip();

            while(buffer.hasRemaining()){
                out.write(buffer);
            }

            buffer.clear();
        }
        long overallT2 =  System.currentTimeMillis();
        System.out.println(String.format("Copied %s KB in %s millisecs", df.format(size2InKB),  (overallT2 - overallT1)));
    }
    catch (IOException e) {
        e.printStackTrace();
    }

    finally {
        close(in);
        close(out);
    }
}

private static void close(Closeable closable)  {
    if (closable != null) {
        try {
            closable.close();
        } catch (IOException e) {
            if (FastCopy.debug)
                e.printStackTrace();
        }    
    }
}

}

Android 및 Java의 모든 버전에서 빠르고 작업 가능:

private void copy(final File f1, final File f2) throws IOException {
    f2.createNewFile();

    final RandomAccessFile file1 = new RandomAccessFile(f1, "r");
    final RandomAccessFile file2 = new RandomAccessFile(f2, "rw");

    file2.getChannel().write(file1.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, f1.length()));

    file1.close();
    file2.close();
}

조금 늦었지만, 다양한 파일 복사 방법을 사용하여 파일을 복사하는데 걸리는 시간을 비교해 보겠습니다.저는 이 방법을 10번 반복해서 평균을 냈습니다.IO 스트림을 사용한 파일 전송은 최악의 후보인 것 같습니다.

Comparison of file transfer using various methods

방법은 다음과 같습니다.

private static long fileCopyUsingFileStreams(File fileToCopy, File newFile) throws IOException {
    FileInputStream input = new FileInputStream(fileToCopy);
    FileOutputStream output = new FileOutputStream(newFile);
    byte[] buf = new byte[1024];
    int bytesRead;
    long start = System.currentTimeMillis();
    while ((bytesRead = input.read(buf)) > 0)
    {
        output.write(buf, 0, bytesRead);
    }
    long end = System.currentTimeMillis();

    input.close();
    output.close();

    return (end-start);
}

private static long fileCopyUsingNIOChannelClass(File fileToCopy, File newFile) throws IOException
{
    FileInputStream inputStream = new FileInputStream(fileToCopy);
    FileChannel inChannel = inputStream.getChannel();

    FileOutputStream outputStream = new FileOutputStream(newFile);
    FileChannel outChannel = outputStream.getChannel();

    long start = System.currentTimeMillis();
    inChannel.transferTo(0, fileToCopy.length(), outChannel);
    long end = System.currentTimeMillis();

    inputStream.close();
    outputStream.close();

    return (end-start);
}

private static long fileCopyUsingApacheCommons(File fileToCopy, File newFile) throws IOException
{
    long start = System.currentTimeMillis();
    FileUtils.copyFile(fileToCopy, newFile);
    long end = System.currentTimeMillis();
    return (end-start);
}

private static long fileCopyUsingNIOFilesClass(File fileToCopy, File newFile) throws IOException
{
    Path source = Paths.get(fileToCopy.getPath());
    Path destination = Paths.get(newFile.getPath());
    long start = System.currentTimeMillis();
    Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
    long end = System.currentTimeMillis();

    return (end-start);
}

NIO 채널 클래스를 사용하는 동안 볼 수 있는 유일한 단점은 중간 파일 복사 진행 상황을 표시하는 방법을 여전히 찾을 수 없다는 것입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top