Java에서 파일을 복사하는 표준 간결한 방법은 무엇입니까?
문제
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"입니다.
링크된 기사 중 하나는 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
파일 복사와 같이 일반적이고 간단한 것을 표준화하는 데 그렇게 오랜 시간이 걸렸다는 것을 믿을 수 없습니다.
위 코드에는 세 가지 가능한 문제가 있습니다.
- getChannel에서 예외가 발생하면 공개 스트림이 누출될 수 있습니다.
- 대용량 파일의 경우 OS가 처리할 수 있는 것보다 더 많은 파일을 한 번에 전송하려고 할 수 있습니다.
- transferFrom의 반환 값을 무시하고 있으므로 파일의 일부만 복사할 수 있습니다.
이는 이유 org.apache.tools.ant.util.ResourceUtils.copyResource
너무 복잡해요.또한 transferFrom은 정상이지만 Linux의 JDK 1.4에서는 transferTo가 중단됩니다(참조: 버그 ID:5056395) – 제시 글릭 얀
이미 Spring을 사용하는 웹 애플리케이션에 있고 간단한 파일 복사를 위해 Apache Commons IO를 포함하지 않으려면 다음을 사용할 수 있습니다. FileCopyUtils Spring 프레임워크의 일부입니다.
한 줄의 코드로 파일을 쉽게 복사할 수 있는 세 가지 방법이 있습니다!
자바7:
private static void copyFileUsingJava7Files(File source, File dest) throws IOException {
Files.copy(source.toPath(), dest.toPath());
}
아파치 커먼즈 IO:
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 스트림을 사용한 파일 전송은 최악의 후보인 것 같습니다.
방법은 다음과 같습니다.
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 채널 클래스를 사용하는 동안 볼 수 있는 유일한 단점은 중간 파일 복사 진행 상황을 표시하는 방법을 여전히 찾을 수 없다는 것입니다.