Копирование файлов на Java
Копирование файла или каталога было типичной задачей разработки. С появлением контейнеров Docker и стремлением к максимальной неизменяемости мы видим это все реже и реже.
Тем не менее, это фундаментальная концепция, и было бы полезно знать, какие варианты есть у разработчика, когда им нужно скопировать файл.
Потоки ввода / вывода
До Java 1.5 стандартным способом копирования файла было использование потоков ввода-вывода :
private void copyFile(File src, File dest) throws IOException {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new FileInputStream(source);
outputStream = new FileOutputStream(dest);
// the size of the buffer doesn't have to be exactly 1024 bytes, try playing around with this number and see what effect it will have on the performance
byte[] buffer = new byte[1024];
int length = 0;
while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
} finally {
is.close();
os.close();
}
}
Каналы и java.nio
После крупного рефакторинга платформы Java и выпуска Java 1.4 в 2000
году был представлен java.nio
Процесс File
стал выглядеть более элегантно и компактно:
private void copyFileUsingChannel(File src, File dest) throws IOException {
FileChannel sourceChannel = null;
FileChannel destinationChannel = null;
try {
sourceChannel = new FileInputStream(src).getChannel();
destinationChannel = new FileOutputStream(dest).getChannel();
destinationChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
} finally {
sourceChannel.close();
destinationChannel.close();
}
}
Наиболее заметное отличие состоит в том, что мы избавились от цикла while и больше не контролируем размер буфера напрямую.
Класс помощника по файлам
Вместе с Java 7, выпущенной в 2011 году, был добавлен вспомогательный класс, позволяющий копировать файлы в одну строку кода:
private static void copyFile(File src, File dest) throws IOException {
Files.copy(src.toPath(), dest.toPath());
}
Взглянув на
документацию
по API, мы видим, что можно предоставить дополнительную опцию для метода
copy()
Поддерживаются следующие параметры из перечислений StandardCopyOption
и LinkOption
:
-
REPLACE_EXISTING - выполняет копирование, даже если целевой файл уже существует. Если целью является символическая ссылка, копируется сама ссылка (а не цель ссылки). Если целью является непустой каталог, копирование завершается ошибкой с исключением FileAlreadyExistsException.
-
COPY_ATTRIBUTES - копирует атрибуты файла, связанные с файлом, в целевой файл. Точные поддерживаемые атрибуты файлов зависят от файловой системы и платформы, но время последнего изменения поддерживается на всех платформах и копируется в целевой файл.
-
NOFOLLOW_LINKS - указывает, что по символическим ссылкам не следует переходить. Если файл, который нужно скопировать, является символической ссылкой, копируется ссылка (а не цель ссылки).
Apache Commons IO и Google Guava
Эти две очень удобные вспомогательные библиотеки предоставляют очень
похожий API для копирования файлов. Единственным недостатком
использования любого из них является необходимость добавления в проект
.jar
Но если он уже там, то почему бы и нет?
private void copyFileApacheCommons(File src, File dest) throws IOException {
FileUtils.copyFile(src, dest);
}
private void copyFileGoogleGuava(File src, File dest) throws IOException {
Files.copy(src, dest);
}
Бонус
Подождите, но что у меня есть String, представляющий имя файла, а не фактический объект File?
Не беспокойтесь, у нас это есть!
private File filenameToFile(String filename) throws IOException {
return new File(filename);
}
Какой из них использовать?
Потоки и каналы ввода-вывода
- это жизнеспособный вариант, чтобы поэкспериментировать и понять, как процесс выглядит изнутри.
Другой вариант - если вы работаете над унаследованным проектом с устаревшей версией Java, и его обновление невозможно. Во всех остальных случаях вы должны проверить, есть ли Apache Commons или Google Guava уже в списке зависимостей, и выбрать одну из них.
Если это не так, вам не следует добавлять их ради этого единственного
служебного метода и использовать вспомогательный класс Files