Меня всегда беспокоило, что единственный способ скопировать файл на Java - это открытие потоков, объявление буфера, чтение одного файла, его просмотр и запись в другой Steam. Интернет изобилует похожими, но все же немного разными реализациями этого типа решений.
Есть ли лучший способ, который не выходит за рамки языка Java (то есть не требует выполнения команд, специфичных для ОС)? Возможно, в каком-нибудь надежном пакете утилит с открытым исходным кодом это, по крайней мере, затмевает эту базовую реализацию и обеспечивает решение в одну строку?
Если вы используете Java 7, используйте вместо этого Files.copy, как рекомендовано @GlenBest: stackoverflow.com/a/16600787/44737




Как упоминалось выше в наборе инструментов, Apache Commons IO - это правильный выбор, в частности FileUtils.копировать файл(); он берет на себя всю тяжелую работу за вас.
И в качестве постскриптума обратите внимание, что в последних версиях FileUtils (например, в выпуске 2.0.1) добавлено использование NIO для копирования файлов; NIO может значительно повысить производительность копирования файлов, в значительной степени потому, что подпрограммы NIO откладывают копирование непосредственно в ОС / файловую систему, а не обрабатывают его путем чтения и записи байтов через уровень Java. Поэтому, если вы ищете производительность, возможно, стоит проверить, что вы используете последнюю версию FileUtils.
Очень полезно - знаете ли вы, когда в официальный выпуск войдут эти изменения nio?
Понятия не имею ... Я просмотрел все источники общедоступной информации, которые смог найти, и нигде не упоминается о возможной дате выпуска. Однако сайт JIRA показывает только пять открытых проблем, так что, может быть, скоро?
Это тоже очень быстро, я заменил часть кода, который мы выполняли xCopy, чтобы скопировать некоторые каталоги, и это очень здорово увеличило скорость. (Я использовал версию из репозитория)
Публичный выпуск Apache Commons IO по-прежнему на уровне 1.4, grrrrrrr
По состоянию на декабрь 2010 года Apache Commons IO находится на уровне 2.0.1, который имеет функциональность NIO. Ответ обновлен.
Предупреждение для пользователей Android: это НЕ входит в стандартные API Android.
Если вы используете Java 7 или новее, вы можете использовать Files.copy, как предлагает @GlenBest: stackoverflow.com/a/16600787/44737
Я бы избегал использования мега api, такого как apache commons. Это упрощенная операция, встроенная в JDK в новом пакете NIO. Это уже было связано с предыдущим ответом, но ключевым методом в API NIO являются новые функции «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.
Спасибо, полезная информация. Я бы все равно выступал за что-то вроде Apache Commons, особенно если он использует nio (правильно) под ним; но я согласен, что важно понимать основные принципы.
Я согласен, что важно понимать предысторию nio, но я все равно выберу Jakarta Commons.
К сожалению, есть предостережения. Когда я скопировал файл размером 1,5 Гб в 32-битной Windows 7, он не смог сопоставить файл. Пришлось искать другое решение.
Три возможных проблемы с приведенным выше кодом: (а) если getChannel выдает исключение, вы можете утечь открытый поток; (б) для больших файлов вы можете попытаться передать за один раз больше, чем может обработать ОС; (c) вы игнорируете возвращаемое значение transferFrom, поэтому возможно копирование только части файла. Вот почему org.apache.tools.ant.util.ResourceUtils.copyResource настолько сложен. Также обратите внимание, что хотя transferFrom в порядке, transferTo прерывается на JDK 1.4 в Linux: bugs.sun.com/bugdatabase/view_bug.do?bug_id=5056395
Я считаю, что эта обновленная версия решает эти проблемы: gist.github.com/889747
@Pete, похоже, Apache Commons еще не использует nio для копирования файлов.
@Mark Renouf: Я получаю неоднозначные результаты с этим кодом. Не уверен, что происходит, но скопированные файлы иногда имеют нулевые байты.
В этом коде есть проблема основной. transferTo () должен вызываться в цикле. Это не гарантирует перевод всей запрошенной суммы.
@EJP Я больше не занимаюсь разработкой на Java. Возможно, вы правы, поэтому можете предложить правильный путь.
В Windows Server я обнаружил, что файлы, скопированные таким образом с помощью NIO, не могут быть переименованы или удалены даже после вызова close () для всего. Я считаю, что это связано с отображениями памяти, которые создает NIO, требуя сборки мусора, как описано в эта почта.
Обратите внимание, что все эти механизмы копируют только содержимое файла, а не метаданные, такие как разрешения. Поэтому, если вы скопируете или переместите исполняемый файл .sh в Linux, новый файл не будет исполняемым.
Чтобы действительно скопировать или переместить файл, т.е. получить тот же результат, что и при копировании из командной строки, вам действительно нужно использовать собственный инструмент. Либо сценарий оболочки, либо JNI.
Судя по всему, это могло быть исправлено в java 7 - http://today.java.net/pub/a/today/2008/07/03/jsr-203-new-file-apis.html. Скрещенные пальцы!
Доступно как стандарт в Java 7, path.copyTo: http://openjdk.java.net/projects/nio/javadoc/java/nio/file/Path.htmlhttp://java.sun.com/docs/books/tutorial/essential/io/copy.html
Не могу поверить, что им потребовалось так много времени, чтобы стандартизировать что-то настолько распространенное и простое, как копирование файлов :(
Нет Path.copyTo; это Files.copy.
В библиотеке Google Guava также есть метод копирования:
public static void copy(File from, File to) throws IOException
Предупреждение: Если to представляет существующий файл, этот файл
будет перезаписан содержимым from. Если to и
from относится к файлу одно и тоже, содержимое этого файла
будет удален.
Параметры:from - исходный файл to - целевой файл
Броски:IOException - при возникновении ошибки ввода / вывода
IllegalArgumentException - если from.equals(to)
Теперь с 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() );
}
}
Или, что еще лучше, это также можно сделать с помощью нового класса Files, представленного в Java 7:
public static void copyFile( File from, File to ) throws IOException {
Files.copy( from.toPath(), to.toPath() );
}
Довольно шикарно, а?
Удивительно, что Java не добавляла подобных вещей до сегодняшнего дня. Определенные операции - это только самое необходимое для написания компьютерного программного обеспечения. Разработчики Oracle для Java могли бы кое-чему научиться у операционных систем, глядя на то, какие услуги они предоставляют, чтобы сделать миграцию новичков ЛЕГЧЕ.
Ах, спасибо! Я не знал о новом классе «Files» со всеми его вспомогательными функциями. В нем есть именно то, что мне нужно. Спасибо за пример.
с точки зрения производительности, java NIO FileChannel лучше, прочтите эту статью journaldev.com/861/4-ways-to-copy-file-in-java
В этом коде есть проблема основной. transferTo () должен вызываться в цикле. Это не гарантирует перевод всей запрошенной суммы.
@Scott: Пит попросил однострочное решение, и вы так близко ... нет необходимости заключать Files.copy в метод copyFile. Я бы просто поместил Files.copy (Path from, Path to) в начало вашего ответа и упомянул, что вы можете использовать File.toPath (), если у вас есть существующие объекты File: Files.copy (fromFile.toPath (), toFile.toPath ())
Три возможных проблемы с приведенным выше кодом:
Вот почему org.apache.tools.ant.util.ResourceUtils.copyResource настолько сложен. Также обратите внимание, что хотя transferFrom в порядке, transferTo прерывается на JDK 1.4 в Linux (см. ID ошибки: 5056395) - Джесси Глик Ян
Если вы находитесь в веб-приложении, которое уже использует Spring, и если вы не хотите включать Apache Commons IO для простого копирования файлов, вы можете использовать FileCopyUtils среды Spring.
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.file, а не java.nio). Я отправил правку для этого; надеюсь, что все в порядке!
Нет смысла писать new java.io.File("<filepath1>").toPath(), если вы вообще можете использовать Paths.get("<filepath1>").
Чтобы скопировать файл и сохранить его по пути назначения, вы можете использовать метод ниже.
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();
}
}
Это сработает, но я не думаю, что это лучше, чем другие ответы здесь?
@Rup Это значительно лучше, чем другие ответы здесь, (а) потому что, он работает, и (б) потому, что он не полагается на стороннее программное обеспечение.
@EJP Хорошо, но это не очень умно. Копирование файла должно быть операцией ОС или файловой системы, а не приложением: мы надеемся, что Java сможет обнаружить копию и превратить ее в операцию ОС, за исключением случаев, когда вы явно читаете файл, когда вы останавливаете его выполнение. Если вы не думаете, что Java может это сделать, доверяете ли вы ей в оптимизации чтения и записи размером 1 КБ в более крупные блоки? И если источник и место назначения находились на удаленном общем ресурсе в медленной сети, то это явно делает ненужную работу. Да, некоторые сторонние JAR-файлы слишком большие (Guava!), Но они добавляют много подобного, сделанного правильно.
Работал как шарм. Лучшее решение, не требующее сторонних библиотек и работающее на java 1.6. Спасибо.
@Rup Я согласен с тем, что это должна быть функция операционной системы, но я не могу понять ваш комментарий иначе. В части после первого двоеточия где-то отсутствует глагол; Я бы тоже не стал "доверять" и ожидать, что Java превратит 1k блоков во что-то большее, хотя я бы сам определенно использовал гораздо большие блоки; Я бы никогда не написал приложение, которое в первую очередь использовало бы общие файлы; и я не знаю, что какая-либо сторонняя библиотека делает что-то более «правильное» (что бы вы под этим ни подразумевали), чем этот код, за исключением, вероятно, использования большего буфера.
@EJP Под «правильным» я имел в виду 1) использование лучших доступных API, например. более новые IO, где это возможно 2) представление вызовов Java таким образом, чтобы он мог легко распознать, что это копия файла - например, каналы, представляющие целые файлы для чтения и записи, как и в других ответах здесь, и превращают их в один вызов ОС. Я думаю, что этот подход настолько медленный, насколько это возможно, особенно с небольшим буфером.
Этот код неверен. InputStream.read может вернуть 0, даже если данных больше. InputStream.read возвращает -1, если данных больше нет. Таким образом, цикл должен завершиться, когда он вернет -1, а не 0.
Быстро и работает со всеми версиями 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();
}
Не все файловые системы поддерживают файлы с отображением памяти, и я думаю, что для небольших файлов это относительно дорого.
Не работает ни с одной версией Java до 1.4, и нет ничего, что могло бы гарантировать, что одной записи будет достаточно.
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();
}
}
}
Таким образом, отличие от принятого ответа состоит в том, что у вас есть transferFrom в цикле while?
Даже не компилируется, а вызов createNewFile () избыточен и расточителен.
В Java 7 это просто ...
File src = new File("original.txt");
File target = new File("copy.txt");
Files.copy(src.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
Что ваш ответ добавляет к ответам Скотта или Глена?
Это кратко, лучше меньше, да лучше. Их ответы хороши и подробны, но я их пропустил при просмотре. К сожалению, на этот вопрос есть много ответов, и многие из них длинные, устаревшие и сложные, и хорошие ответы Скотта и Глена в этом потерялись (я дам положительные голоса, чтобы помочь с этим). Интересно, можно ли улучшить мой ответ, сократив его до трех строк, выбив exists () и сообщение об ошибке.
Это не работает для каталогов. Блин, все ошибаются. Больше из-за связи API проблема ваша ошибка. Я тоже ошибся.
@momo вопрос был как скопировать файл.
Если вам нужен File, не нужно объезжать Path. Files.copy(Paths.get("original.txt"), Paths.get("copy.txt"), …)
Вот три способа, с помощью которых вы можете легко копировать файлы с помощью одной строки кода!
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);
}
Первый не работает для каталогов. Блин, все ошибаются. Больше из-за связи API проблема ваша ошибка. Я тоже ошибся.
Сначала нужно 3 параметра. Files.copy, использующий только 2 параметра, предназначен для Path - Stream. Просто добавьте параметр StandardCopyOption.COPY_ATTRIBUTES или StandardCopyOption.REPLACE_EXISTING для Path в Path
Копия 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.io .. копирование 10 ГБ всего за 160 секунд
Немного поздно для вечеринки, но вот сравнение времени, затраченного на копирование файла с использованием различных методов копирования файлов. Я повторил методы 10 раз и взял среднее значение. Передача файлов с использованием потоков ввода-вывода кажется худшим кандидатом:
Вот методы:
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, заключается в том, что я все еще не могу найти способ показать прогресс копирования промежуточных файлов.
Там могло быть что-то в Apache Commons FileUtils, в частности методы копировать файл.