Java로 파일을 복사하는 표준 간결한 방법?


421

Java에서 파일을 복사하는 유일한 방법은 스트림 열기, 버퍼 선언, 한 파일 읽기, 반복 및 다른 스팀에 쓰는 것만으로 항상 귀찮았습니다. 웹은 이러한 유형의 솔루션의 유사하지만 여전히 약간 다른 구현으로 흩어져 있습니다.

Java 언어의 범위 내에 머무르는 더 좋은 방법이 있습니까 (즉, OS 특정 명령 실행과 관련이 없음)? 아마도 일부 신뢰할 수있는 오픈 소스 유틸리티 패키지에서이 기본 구현을 모호하게하고 한 줄 솔루션을 제공 할 것입니까?


5
Apache Commons FileUtils 에 특히 copyFile 메소드 가있을 수 있습니다 .
툴킷

22
@GlenBest에서 권장하는 자바 7을 사용하는 경우, 대신 Files.copy를 사용 stackoverflow.com/a/16600787/44737
강탈

답변:


274

툴킷이 위에서 언급했듯이 Apache Commons IO는 특히 FileUtils 입니다. copyFile () ; 그것은 당신을 위해 모든 무거운 리프팅을 처리합니다.

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


1
매우 유용합니다-공식 출시에 이러한 nio 변경 사항이 포함될시기에 대한 통찰력이 있습니까?
Peter

2
1.4에서 여전히 Apache Commons IO의 공개 릴리스, grrrrrrr
Peter

14
2010 년 12 월 현재 Apache Commons IO는 2.0.1이며 NIO 기능이 있습니다. 답변이 업데이트되었습니다.
Simon Nickerson

4
안드로이드 사용자에게 경고 : 이것은 표준 안드로이드 API에 포함되어 있지 않습니다
IlDan

18
Java 7 이상을 사용하는 경우 @GlenBest에서 제안한대로 Files.copy를 사용할 수 있습니다. stackoverflow.com/a/16600787/44737
rob

278

나는 아파치 커먼즈와 같은 메가 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에 소개 된 경우 배우기가 매우 어려울 수 있습니다.


2
감사합니다. 유용한 정보입니다. 나는 아직도 Apache Commons와 같은 것을 주장 할 것이다. 그러나 기본 원리를 이해하는 것이 중요하다는 데 동의합니다.
Peter

1
불행히도 경고가 있습니다. Windows 7, 32 비트에서 1.5Gb 파일을 복사 할 때 파일을 매핑하지 못했습니다. 다른 해결책을 찾아야했습니다.
Anton K.

15
위 코드에서 가능한 세 가지 문제 : (a) getChannel에서 예외가 발생하면 열린 스트림이 누출 될 수 있습니다. (b) 큰 파일의 경우 OS가 처리 할 수있는 것보다 한 번에 더 많은 양을 전송하려고 할 수 있습니다. (c) transferFrom의 반환 값을 무시하고 있으므로 파일의 일부만 복사하는 것일 수 있습니다. org.apache.tools.ant.util.ResourceUtils.copyResource가 너무 복잡한 이유입니다. 또한 transferFrom은 정상이지만 Linux의 JDK 1.4에서는 transferTo가 중단됩니다. bugs.sun.com/bugdatabase/view_bug.do?bug_id=5056395
Jesse Glick

7
이 업데이트 버전이 이러한 문제를 해결한다고 생각합니다. gist.github.com/889747
Mark Renouf

11
이 코드에는 문제가 있습니다. transferTo ()는 루프에서 호출되어야합니다. 요청 된 전체 금액을 양도한다고 보장하지는 않습니다.
Lorne의 후작

180

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

꽤 멋져요?


15
Java가 오늘 전에 이와 같은 것을 추가하지 않은 것은 놀라운 일입니다. 특정 작업은 컴퓨터 소프트웨어를 작성하는 데 절대적으로 필요한 요소입니다. Java의 Oracle 개발자는 운영 체제에서 제공하는 서비스를 통해 초보자가 쉽게 마이그레이션 할 수 있도록 운영 체제에서 한 가지 또는 두 가지를 배울 수 있습니다.
Rick Hodgin

2
아 고마워! 모든 도우미 함수가 포함 된 새로운 "파일"클래스를 알지 못했습니다. 그것은 내가 필요한 것을 정확히 가지고 있습니다. 예를 주셔서 감사합니다.
ChrisCantrell

1
성능 현명한, 자바 NIO되는 FileChannel이 더,이 문서 읽기 journaldev.com/861/4-ways-to-copy-file-in-java
판 카즈

5
이 코드에는 문제가 있습니다. transferTo ()는 루프에서 호출되어야합니다. 요청 된 전체 금액을 양도한다고 보장하지는 않습니다.
Lorne의 후작

@ 스콧 : 피트는 한 줄 솔루션을 요청했으며 너무 가깝습니다 .file.copy를 copyFile 메소드로 감싸는 것은 필요하지 않습니다. 대답의 시작 부분에 Files.copy (Path from, Path to)를 넣고 기존 File 객체가있는 경우 File.toPath ()를 사용할 수 있다고 언급했습니다. Files.copy (fromFile.toPath (), toFile.toPath ())
강탈

89
  • 이러한 방법은 성능을 고려하여 설계되었습니다 (운영 체제 고유 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.nio가 아닌 java.nio.file이어야 함). 이에 대한 수정 사항을 제출했습니다. 괜찮 으시길 바랍니다!
Stuart Rossiter

43

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

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

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

1
귀하의 답변은 Scott 또는 Glen에 무엇을 추가합니까?
Uri Agassi

11
간결합니다. 그들의 대답은 훌륭하고 상세하지만, 살펴볼 때보고 싶었습니다. 불행히도 이것에 대한 많은 답변이 있으며 그중 많은 것들이 길고 쓸모없고 복잡하며 스콧과 글렌의 좋은 답변은 그에서 잃어 버렸습니다 (나는 그것을 돕기 위해 공의를 포기할 것입니다). exist () 및 오류 메시지를 제거하여 3 줄로 줄이면 대답이 향상 될지 궁금합니다.
Kevin Sadler

디렉토리에서는 작동하지 않습니다. 젠장 모두 가이 하나를 잘못 받고 있습니다. 더 많은 API 통신으로 결함이 발생합니다. 나도 틀렸다.
mmm

2
@momo 문제는 파일을 복사하는 방법이었습니다.
Kevin Sadler

28

파일을 복사하여 대상 경로에 저장하려면 아래 방법을 사용하십시오.

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

1
이것은 효과가 있지만, 다른 답변보다 낫다고 생각하지 않습니까?
Rup

2
@Rup 여기의 다른 답변보다 훨씬 낫습니다. (a) 작동 하기 때문에 (b) 타사 소프트웨어에 의존하지 않기 때문에.
Lorne의 후작

1
@ EJP OK,하지만 똑똑하지 않습니다. 파일 복사는 응용 프로그램 작업이 아닌 OS 또는 파일 시스템 작업이어야합니다. Java는 파일을 명시 적으로 읽지 않고 복사를 발견하여 OS 작업으로 전환 할 수 있기를 바랍니다. Java가 그렇게 할 수 없다고 생각한다면 1K 읽기 및 쓰기를 더 큰 블록으로 최적화한다고 믿습니까? 그리고 소스와 대상이 느린 네트워크를 통해 원격 공유에 있었다면 이것은 불필요한 작업을 분명히하고 있습니다. 예, 일부 타사 JAR은 어리석게 큽니다 (Guava!). 그러나 이와 같이 많은 것들을 올바르게 추가합니다.
Rup

매력처럼 일했다. 타사 라이브러리가 필요하지 않고 Java 1.6에서 작동하는 최상의 솔루션입니다. 감사.
James Wierzba

@Rup 운영 체제 기능이어야한다는 데 동의하지만 다른 의견은 없습니다. 첫 번째 콜론 다음 부분에는 어딘가에 동사가 부족합니다. 필자는 자바가 1k 블록을 더 큰 것으로 바꿀 것이라고 기대하지는 않을 것이다. 처음에는 공유 파일을 사용한 응용 프로그램을 작성하지 않을 것입니다. 그리고 더 큰 버퍼를 사용하는 것을 제외하고는 타사 라이브러리 가이 코드보다 더 '적절한'(당신이 의미하는 바를) 수행한다는 것을 알지 못합니다.
Lorne의 후작

24

이러한 모든 메커니즘은 권한과 같은 메타 데이터가 아니라 파일의 내용 만 복사합니다. 따라서 리눅스에서 실행 가능한 .sh 파일을 복사하거나 옮기려면 새 파일을 실행할 수 없습니다.

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

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


23

구글의 구아바 라이브러리에는 복사 방법도 있습니다 :

공공 정적 무효 사본 ( 파일  ,
                         파일  )
                 던졌습니다 IOException이는
한 파일에서 다른 파일로 모든 바이트를 복사합니다.

경고 : Ifto 기존 파일을 나타내는 해당 파일을의 내용으로 덮어 씁니다 from. 경우 tofrom참고하여주십시오 동일한 파일, 해당 파일의 내용이 삭제됩니다.

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

예외 : IOException - I / O 오류가 발생하는 경우 IllegalArgumentException- 경우from.equals(to)



7

위의 코드에서 가능한 세 가지 문제 :

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

이것이 org.apache.tools.ant.util.ResourceUtils.copyResource너무 복잡한 이유 입니다. 또한 transferFrom은 정상이지만 Linux의 JDK 1.4에서는 transferTo가 중단됩니다 ( 버그 ID : 5056395 참조 ) – Jesse Glick Jan


7

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


7

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

자바 :

java.nio.file.Files # copy

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

Appache Commons IO :

FileUtils # copy 파일

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

첫 번째는 디렉토리에서 작동하지 않습니다. 젠장 모두 가이 하나를 잘못 받고 있습니다. 더 많은 API 통신으로 결함이 발생합니다. 나도 틀렸다.
mmm

먼저 3 개의 매개 변수가 필요합니다. Files.copy2 개의 매개 변수 만 사용 Path합니다 Stream. 그냥 매개 변수를 추가 StandardCopyOption.COPY_ATTRIBUTES또는 StandardCopyOption.REPLACE_EXISTING위해 PathPath
포주 Trizkit

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

그래서 가장 많이 받아 들여지는 대답과의 차이점은 whileFrom에서 while 루프를 가지고 있다는 것입니다.
Rup

1
컴파일조차하지 않으며 createNewFile () 호출은 중복되고 낭비입니다.
Lorne의 후작

3

내 테스트에 따르면 버퍼가있는 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();
        }    
    }
}

}


좋은! 이것 만 160초에 10 기가 바이트 복사 .. standar을 java.io 스트림보다 빠른 오히려입니다
aswzen

2

모든 Java 버전과 Android에서도 빠르고 작업 할 수 있습니다.

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

1
모든 파일 시스템이 메모리 매핑 파일을 지원하는 것은 아니며 작은 파일의 경우 상대적으로 비싸다고 생각합니다.
Rup

1.4 이전의 모든 Java 버전에서는 작동하지 않으며 단일 쓰기만으로 충분하다는 보장은 없습니다.
Lorne의 후작

1

파티에 조금 늦었지만 여기에 다양한 파일 복사 방법을 사용하여 파일을 복사하는 데 걸리는 시간을 비교했습니다. 나는 방법을 10 번 반복하여 평균을 얻었습니다. IO 스트림을 사용한 파일 전송이 최악의 후보 인 것 같습니다.

다양한 방법을 사용한 파일 전송 비교

방법은 다음과 같습니다.

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 채널 클래스를 사용하는 동안 볼 수있는 유일한 단점은 여전히 ​​중간 파일 복사 진행률을 표시하는 방법을 찾을 수 없다는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.