Эффективный способ рекурсивного перемещения каталогов и их слияния в Java.

Я ищу наиболее эффективный способ рекурсивного перемещения каталога в Java. На данный момент я использую Apache commons-io, как показано в коде ниже. (Если destDir существует и содержит часть файлов, я бы хотел, чтобы они были перезаписаны, а вложенные структуры каталогов были объединены).

FileUtils.copyDirectoryToDirectory(srcDir, destDir);
FileUtils.deleteDirectory(srcDir);

Хотя это помогает, на мой взгляд, это недостаточно эффективно. Есть как минимум две проблемы, которые приходят на ум:

  • Вам понадобится вдвое больше места.
  • Если это SSD, копирование данных на другую часть диска и последующее стирание старых данных в конечном итоге повлияет на аппаратное обеспечение, поскольку фактически сократит срок службы жесткого диска.

Каков наилучший подход для этого?

Насколько я понимаю, commons-io, похоже, не использует новые функции Java 7/8, доступные в Files. С другой стороны, мне не удалось заставить работать Files.move(...), если существует destDir (под "заставить его работать" я имею в виду объединение структур каталогов - он жалуется, что destDir существует).

По поводу сбоев в движении (поправьте меня, если я ошибаюсь):

  • Насколько я понимаю, атомарное перемещение выполняется только в том случае, если все файлы перемещаются одновременно. Если я правильно понимаю, это означает, что это сначала копирование, а затем удаление. Я не думаю, что это то, что я ищу.
  • Если определенный путь/файл нельзя переместить, то операция должна прекратиться и вызвать исключение, сохраняя текущий исходный путь, по которому она была достигнута.

Обратите внимание, что я не ограничиваюсь использованием библиотеки commons-io. Я открыт для предложений. Я использую Java 8.


person carlspring    schedule 15.08.2015    source источник
comment
Что делать с Files.move(source, target)?   -  person Sergey Kalinichenko    schedule 15.08.2015
comment
Вам нужно объединить srcDir с существующим destDir? В противном случае не было бы достаточно простой операции mv srcDir destDir?   -  person Philipp Claßen    schedule 15.08.2015
comment
@dasblinkenlight Это работает только с файлами и пустыми каталогами.   -  person Tunaki    schedule 15.08.2015
comment
Что вы хотите, чтобы произошло, если ход не удастся? Атомные движения непросты.   -  person Thorbjørn Ravn Andersen    schedule 15.08.2015
comment
Это должно выполняться ежедневно, большое количество раз? В наши дни вам не нужно следить за каждой операцией ввода-вывода на SSD.   -  person John    schedule 15.08.2015
comment
@Tunaki Не совсем: если каталог не пуст, перемещение разрешено, когда каталог можно переместить без перемещения содержимого этого каталога.   -  person Sergey Kalinichenko    schedule 15.08.2015
comment
Я не хочу использовать внешний инструмент/команду, например rsync или mv.   -  person carlspring    schedule 15.08.2015
comment
@John: Нет, это будет выполняться по запросу, это не будет запланированное задание.   -  person carlspring    schedule 15.08.2015
comment
@PhilippClaßen: destDir может существовать, а может и не существовать. Если он существует и в нем есть какие-то файлы/каталоги, то они должны быть перезаписаны, а структуры каталогов обоих каталогов должны быть объединены. Это не rsync-подобная синхронизация обеих сторон, это будет слияние, только если существует destDir.   -  person carlspring    schedule 15.08.2015
comment
@ ThorbjørnRavnAndersen: Хороший вопрос. Я полагаю, это должно быть атомарное действие, но разве для этого сначала не потребуется копия? Другой способ — просто остановиться на неудачном пути и выдать ошибку.   -  person carlspring    schedule 15.08.2015
comment
Я думаю, что вы должны тщательно продумать, как все должно работать в случае возникновения проблем (например, повышение безопасности производства). Ответ на ваш вопрос может прийти сам собой, когда вы станете кристально чистыми.   -  person Thorbjørn Ravn Andersen    schedule 15.08.2015


Ответы (3)


Это всего лишь ответ на часть вопроса "что должно произойти с файловой системой", а не как это сделать с Java.

Даже если вы хотите обратиться к внешнему инструменту, Unix mv не похож на Проводник Windows. Каталоги с одинаковыми именами не объединяются. Поэтому вам нужно будет реализовать это самостоятельно или найти библиотечную функцию, которая делает это. Не существует единого системного вызова Unix, который выполняет всю рекурсивную операцию (не говоря уже об атомарности), поэтому это должен делать либо ваш код, либо библиотечная функция.

Если вам нужно атомарно перейти от одной версии дерева к другой, вам нужно построить новое дерево. Файлы могут быть жесткими ссылками на старую версию. то есть сделать эквивалент

cp -al dir/  new
rsync -a /path/to/some/stuff/  new/
# or maybe something smarter / custom that renames instead of copies files.

# your sanity check here

mv  dir old &&
mv  new dir &&   # see below for how to make this properly atomic
rm -rf old

Это оставляет окно, в котором dir не существует. Чтобы решить эту проблему, добавьте уровень косвенности, сделав dir символической ссылкой. Симлинки можно заменять атомарно на mv (но не ln -sf)< /а>. Итак, в Java вам нужно что-то, что в конечном итоге будет выполнять системный вызов rename, а не unlink/rename.


Если у вас нет большого количества очень маленьких файлов (менее 100 байт), операции с метаданными каталога для создания фермы жестких ссылок намного дешевле, чем полная копия дерева каталогов. Данные файла останутся на месте (и даже никогда не будут прочитаны), данные каталога будут новой копией. Метаданные файла (иноды) будут записаны для всех файлов (для обновления ctime и количества ссылок, при создании фермы жестких ссылок и снова при удалении старого дерева, оставляя файлы с исходным количеством ссылок.


Если вы работаете с последним ядром Linux, существует новый (2013) системный вызов ( называется renameat2), который может атомарно обмениваться двумя путями. Это позволяет избежать уровня косвенности символической ссылки. Однако использование системного вызова только для Linux из Java принесет больше проблем, чем пользы, поскольку символические ссылки просты.

person Peter Cordes    schedule 15.08.2015

Я отвечаю на свой вопрос, так как я написал свою собственную реализацию.

Что мне не понравилось в реализации:

  • Apache Commons IO
  • Гуава
  • Спрингфреймворк

для перемещения файлов было то, что все они сначала копируют каталоги и файлы, а потом удаляют их. (Насколько я проверял, сентябрь 2015 г.) Все они, кажется, застряли с методами из JDK 1.6.

Мое решение не атомарно. Он обрабатывает перемещение, просматривая структуру каталогов и выполняя перемещение файл за файлом. Я использую новые методы из JDK 1.7. Это делает работу для меня, и я уверен, что другие люди хотели бы сделать то же самое и задаться вопросом, как это сделать, а затем тратить время. Поэтому я создал небольшой проект на Github, который содержит иллюстрацию:

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

person carlspring    schedule 10.09.2015

Пройдите по дереву исходных каталогов:

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

Тщательно продумайте, как следует обрабатывать любую ошибку.

Обратите внимание, что вы также можете просто вызвать «rsync», если он доступен в вашей системе.

person Thorbjørn Ravn Andersen    schedule 15.08.2015
comment
Звучит неплохо, и теоретически я знаю, что примерно должно произойти. Я ищу пример кода. Конечно, я не первый, кто хочет переместить несколько файлов, эффективно, как в стиле mv. - person carlspring; 15.08.2015
comment
Вы неправильно поняли, как работает stackoverflow. Вы сначала попробуете, а потом люди помогут вам исправить ваш код. - person Thorbjørn Ravn Andersen; 15.08.2015
comment
На самом деле... Я некоторое время работал на SO, возможно, не так долго, как вы, но я понимаю, как это работает. Я могу сам пройтись по структуре каталогов с помощью Files.walk(...) и в конечном итоге реализовать это самостоятельно. Я пытаюсь понять, существует ли это уже в какой-то библиотеке, поскольку, честно говоря, перемещение файлов — это не высшая математика — это повседневная задача. Насколько я понимаю, commons-io не использует новые функции Java 8? В то же время новые функции в Java 8, похоже, не работают хорошо, когда существует целевой каталог. - person carlspring; 15.08.2015