質問
Java でファイルをコピーするには、ストリームを開き、バッファを宣言し、1 つのファイルを読み込んでループし、もう 1 つのストリームに書き出す必要がある唯一の方法が私を常に悩ませてきました。Web には、このタイプのソリューションの類似した、しかしわずかに異なる実装が散在しています。
Java 言語の範囲内にとどまる (OS 固有のコマンドの実行を必要としない) より良い方法はありますか?おそらく、信頼性の高いオープン ソース ユーティリティ パッケージでは、少なくともこの基礎となる実装を曖昧にし、1 行のソリューションを提供するのでしょうか?
解決
ツールキットで前述したように、特に Apache Commons IO が最適です。 ファイルユーティリティ.コピーファイル();面倒な作業はすべて引き受けてくれます。
追記として、FileUtils の最近のバージョン (2.0.1 リリースなど) では、ファイルのコピーに NIO の使用が追加されていることに注意してください。 NIO はファイルコピーのパフォーマンスを大幅に向上させることができます, これは主に、NIO ルーチンが Java 層を介してバイトを読み書きして処理するのではなく、OS/ファイルシステムへの直接コピーを延期するためです。したがって、パフォーマンスを求める場合は、FileUtils の最新バージョンを使用していることを確認する価値があるかもしれません。
他のヒント
Apache Commons のようなメガ API の使用は避けたいと思います。これは単純化された操作であり、新しい NIO パッケージの JDK に組み込まれています。以前の回答ですでにリンクされているようなものですが、NIO API の重要なメソッドは新しい関数「transferTo」と「transferFrom」です。
リンクされた記事の 1 つは、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
ファイルのコピーのような、これほど一般的で単純なものを標準化するのに、これほど長い時間がかかったとは信じられません:(
上記のコードには次の 3 つの問題が考えられます。
- getChannel が例外をスローすると、開いているストリームがリークする可能性があります。
- 大きなファイルの場合は、OS が処理できる量を超える量を一度に転送しようとしている可能性があります。
- transferFrom の戻り値を無視しているため、ファイルの一部だけをコピーしている可能性があります。
これが理由です org.apache.tools.ant.util.ResourceUtils.copyResource
とても複雑です。また、transferFrom は問題ありませんが、Linux 上の JDK 1.4 では transferTo が壊れることにも注意してください (「 バグID:5056395) – ジェシー・グリック・ヤン
すでに Spring を使用している Web アプリケーションを使用していて、単純なファイルのコピーのために Apache Commons IO を組み込みたくない場合は、次のように使用できます。 ファイルコピーユーティリティ Spring フレームワークの。
ここでは、1 行のコードで簡単にファイルをコピーできる 3 つの方法を紹介します。
Java7:
private static void copyFileUsingJava7Files(File source, File dest) throws IOException {
Files.copy(source.toPath(), dest.toPath());
}
Appache Commons 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();
}
}
}
}
高速で、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();
}
少し遅れましたが、さまざまなファイル コピー方法を使用してファイルをコピーするのにかかる時間を比較します。メソッドを 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 チャネル クラスを使用しているときにわかる唯一の欠点は、中間ファイルのコピーの進行状況を表示する方法がまだ見つからないことです。