Стандартный краткий способ копирования файла на Java?

StackOverflow https://stackoverflow.com/questions/106770

  •  01-07-2019
  •  | 
  •  

Вопрос

Меня всегда беспокоило, что единственный способ скопировать файл на Java включает в себя открытие потоков, объявление буфера, чтение в одном файле, зацикливание на нем и запись его в другой steam.Интернет изобилует похожими, но все же немного отличающимися реализациями решений этого типа.

Есть ли лучший способ, который остается в рамках языка Java (то есть не предполагает выполнения специфичных для операционной системы команд)?Возможно, в каком-нибудь надежном пакете утилит с открытым исходным кодом, который, по крайней мере, скрыл бы эту базовую реализацию и предоставил бы однострочное решение?

Это было полезно?

Решение

Как упоминалось выше в toolkit, Apache Commons IO - это правильный путь, в частности FileUtils Файлы.CopyFile() Копировать файл();он берет на себя всю тяжелую работу за вас.

И в качестве постскриптума обратите внимание, что в последних версиях FileUtils (таких как версия 2.0.1) добавлено использование NIO для копирования файлов; NIO может значительно повысить производительность копирования файлов, в значительной степени потому, что процедуры NIO откладывают копирование непосредственно в операционную систему / файловую систему, а не обрабатывают его путем чтения и записи байтов через уровень Java.Поэтому, если вы ищете производительность, возможно, стоит проверить, используете ли вы последнюю версию FileUtils.

Другие советы

Я бы избегал использования mega api, такого как apache commons.Это упрощенная операция, и она встроена в JDK в новом пакете NIO.Это было вроде как уже связано с предыдущим ответом, но ключевым методом в NIO api являются новые функции "transferTo" и "transferFrom".

http://java.sun.com/javase/6/docs/api/java/nio/channels/FileChannel.html#transferTo(long,%20long,%20java.nio.channels.Записываемый байтканал)

В одной из статей по ссылкам показан отличный способ интеграции этой функции в ваш код, используя 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 за одну ночь.Исходя из личного опыта, этому может быть очень сложно научиться, если у вас нет опыта и вы познакомились с IO через java.io streams.

Теперь в 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() );
}

Довольно шикарно, да?

  • Эти методы оптимизированы с точки зрения производительности (они интегрируются с собственным вводом-выводом операционной системы).
  • Эти методы работают с файлами, каталогами и ссылками.
  • Каждая из представленных опций может быть опущена - они необязательны.

Класс полезности

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

Обратите внимание, что все эти механизмы копируют только содержимое файла, а не метаданные, такие как разрешения.Таким образом, если бы вы скопировали или переместили исполняемый файл .sh в linux, новый файл не был бы исполняемым.

Чтобы действительно скопировать или переместить файл, т. е. получить тот же результат, что и при копировании из командной строки, вам действительно нужно использовать собственный инструмент.Либо сценарий оболочки, либо 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 Исключение IOException
Копирует все байты из одного файла в другой.

Предупреждение: Если to представляет существующий файл, этот файл будет перезаписан содержимым from.Если to и from обратитесь к то же самое файл, содержимое этого файла будет удалено.

Параметры:from - исходный файлto - конечный файл

Бросает: Исключение IOException - при возникновении ошибки ввода-вывода Исключение 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

Я не могу поверить, что им потребовалось так много времени, чтобы стандартизировать что-то столь распространенное и простое, как копирование файлов :(

Три возможные проблемы с приведенным выше кодом:

  1. Если getChannel выдает исключение, у вас может произойти утечка открытого потока.
  2. Для больших файлов вы, возможно, пытаетесь передать сразу больше файлов, чем может обработать операционная система.
  3. Вы игнорируете возвращаемое значение transferFrom, поэтому это может быть копирование только части файла.

Вот почему org.apache.tools.ant.util.ResourceUtils.copyResource это так сложно.Также обратите внимание, что хотя transferFrom работает нормально, transferTo прерывается в JDK 1.4 в Linux (см. Идентификатор ошибки: 5056395) – Джесси Глик , Ян

Если вы работаете в веб-приложении, которое уже использует Spring, и если вы не хотите включать Apache Commons IO для простого копирования файлов, вы можете использовать Файлообменники из пружинного каркаса.

Вот три способа, с помощью которых вы можете легко копировать файлы с помощью одной строки кода!

Java7:

java.nio.файл.Файлы#копировать

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

Appache Commons Ввод-вывод:

FileUtils#Копировать файл

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 раз и получил среднее значение.Передача файлов с использованием потоков ввода-вывода, по-видимому, является худшим кандидатом:

Comparison of file transfer using various methods

Вот эти методы:

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 channel, заключается в том, что я все еще не могу найти способ показать промежуточный ход копирования файла.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top