一直困扰我的是,在Java中复制文件的唯一方法包括打开流,声明缓冲区,读入一个文件,循环遍历它,并将其写入到另一个steam。网络上充斥着类似的,但仍然略有不同的这类解决方案的实现。
是否有一种更好的方法能够保持在Java语言的范围内(即不涉及执行特定于操作系统的命令)?也许在一些可靠的开源实用程序包中,它至少会模糊这个底层实现,并提供一行解决方案?
一直困扰我的是,在Java中复制文件的唯一方法包括打开流,声明缓冲区,读入一个文件,循环遍历它,并将其写入到另一个steam。网络上充斥着类似的,但仍然略有不同的这类解决方案的实现。
是否有一种更好的方法能够保持在Java语言的范围内(即不涉及执行特定于操作系统的命令)?也许在一些可靠的开源实用程序包中,它至少会模糊这个底层实现,并提供一行解决方案?
当前回答
上述代码有三个可能的问题:
如果getChannel抛出异常,则可能泄漏一个开放流。 对于大文件,您可能会尝试一次传输超出操作系统处理能力的文件。 您忽略了transferFrom的返回值,因此它可能只是复制文件的一部分。
这就是为什么org.apache.tools.ant.util.ResourceUtils.copyResource如此复杂。还要注意,虽然transferFrom没问题,但transferTo会在Linux上的JDK 1.4上中断(参见Bug ID:5056395)
其他回答
根据我的测试,带缓冲区的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 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() );
}
很时髦,是吧?
虽然有点晚了,但是这里比较了使用各种文件复制方法复制一个文件所花费的时间。我循环使用这些方法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通道类时看到的唯一缺点是,我似乎仍然无法找到一种方法来显示中间文件复制进度。
谷歌的Guava库也有一个复制方法:
公共静态无效复制(文件从, 文件) 抛出IOException 将所有字节从一个文件复制到另一个文件。
警告:如果to表示已存在的文件,则该文件 将被from的内容覆盖。如果是和 从引用同一个文件,该文件的内容 将被删除。 参数:从-源文件到-目标文件 抛出: IOException -如果发生I/O错误 IllegalArgumentException - if from.equals(to)
上述代码有三个可能的问题:
如果getChannel抛出异常,则可能泄漏一个开放流。 对于大文件,您可能会尝试一次传输超出操作系统处理能力的文件。 您忽略了transferFrom的返回值,因此它可能只是复制文件的一部分。
这就是为什么org.apache.tools.ant.util.ResourceUtils.copyResource如此复杂。还要注意,虽然transferFrom没问题,但transferTo会在Linux上的JDK 1.4上中断(参见Bug ID:5056395)