Как атомарно заменить каталог другим в Java?

У меня есть каталог, содержащий файлы данных, обслуживаемые клиентами, например /srv/data. Выполняя серию обновлений, я работаю над /srv/data_tmp, и в конце операции я хотел бы атомарно заменить data на data_tmp. File.renameTo() для меня всегда возвращает false, если местом назначения является существующий каталог. Как я могу это сделать?


person Jean-Philippe Pellet    schedule 08.12.2010    source источник
comment
Есть ли у вас какие-либо ограничения для базовой файловой системы. Очевидно, что если это FAT, то нет никакого выхода, поскольку файловая система не поддерживает его. Вы знаете, находятся ли эти два каталога на одном физическом томе? Можете ли вы использовать новые API-интерфейсы JDK7 java.nio.file или они должны работать с JDK ‹= 6?   -  person Mike Samuel    schedule 08.12.2010
comment
Я не думаю, что две операции перемещения файлов могут выполняться атомарно, в строгом смысле этого слова. Некоторые файловые системы гарантируют выполнение некоторых атомарных операций, но я не знаю никакого способа запрос к ОС, чтобы несколько действий ФС выполнялись атомарно для всех процессов; уж точно не на Яве.   -  person maerics    schedule 08.12.2010
comment
@Mike: Я буду использовать HFS +, ext3 или NTFS, но не FAT. Он должен работать с JDK 6.   -  person Jean-Philippe Pellet    schedule 08.12.2010
comment
@Mike: и два каталога всегда будут на одном физическом томе   -  person Jean-Philippe Pellet    schedule 08.12.2010


Ответы (4)


Боюсь, ты не сможешь. По крайней мере, не на уровне SO. Таким образом, даже если вы управляете «атомарностью» в контексте вашего java-приложения, у вас нет гарантии, что какой-то другой «мошеннический» процесс вмешивается на фактический уровень файловой системы.

На вашем месте я бы прочитал эту статью (довольно старый, но должен дать вам некоторые идеи), а затем посмотрите, сможете ли вы перенести предложенный подход на более современная версия.

Ой, подождите, кто-то уже сделал это!

И, очевидно, вы не первый, кто задает здесь вопрос,

Удачи...

person p.marino    schedule 08.12.2010
comment
Транзакции Apache Commons позволяют мне делать это очень аккуратно, хотя и только в контексте моего приложения Java. Этого мне сейчас достаточно. Спасибо за ссылку! - person Jean-Philippe Pellet; 09.12.2010

Вы можете заменить каталог /srv/data символической ссылкой (или переходом в Windows XP) и при необходимости измените цель ссылки. Однако вы не сможете сделать это с помощью API Java 6 - вам придется полагаться на библиотеку или самостоятельно писать команды командной строки.

NB: Я не гарантирую ничего относительно атомарности этой операции.

person Jean Hominal    schedule 08.12.2010
comment
Подход символической ссылки интересен, но вы ошибаетесь, говоря, что вы не можете переименовать файл в файл, который уже существует. Если и a, и b существуют в /home/me, тогда это успешно заменяет b на a (в Mac OS X / HFS +): new File("/home/me/a").renameTo(new File("/home/me/b") - person Jean-Philippe Pellet; 08.12.2010

Системный вызов rename Linux не позволяет этого (системный вызов rename может перезаписать только пустой каталог), поэтому я сомневаюсь, что это возможно сделать на Java в Linux.

person Ken Bloom    schedule 08.12.2010
comment
Является ли вызов rename атомарным? И если да, то также в случае перезаписи? - person Jean-Philippe Pellet; 08.12.2010
comment
Да, но согласно странице руководства при перезаписи, вероятно, появится окно, в котором как oldpath, так и newpath относятся к переименованному файлу. - person Ken Bloom; 08.12.2010

Достижение этой цели вполне возможно с помощью комбинации «символической ссылки» и «переименования» вместе с промежуточным каталогом tmp. Следующий пример находится в оболочке, но вы можете легко перевести функциональность здесь, чтобы использовать базовые вызовы:

mkdir -p tmp/real_dir1 tmp/real_dir2
touch tmp/real_dir1/a tmp/real_dir2/a
# start with ./target_dir pointing to tmp/real_dir1
ln -s tmp/real_dir1 target_dir
# create a symlink named target_dir in tmp, pointing to real_dir2
ln -sf tmp/real_dir2 tmp/target_dir
# atomically mv it into ./ replacing ./target_dir
mv tmp/target_dir ./

Пример взят из: http://axialcorps.wordpress.com/2013/07/03/atomically-replacing-files-and-directories/

Это сводится к (в псевдокоде):

mkdir('./tmp');
mkdir('./tmp/real_dir1');
mkdir('./tmp/real_dir2');
symlink('./tmp/real_dir1', './target_dir')
symlink('./tmp/real_dir2', './tmp/target_dir')
rename('./tmp/target_dir', './target_dir')

Окончательное переименование здесь является атомарным, поэтому действие будет либо успешным, либо полностью провальным, с точки зрения любого процесса, использующего каталог, действие является атомарным.

person mssaxm    schedule 05.07.2013