Перемещение файлов после неудачной проверки (Java)

Мы проверяем XML-файлы, и в зависимости от результата проверки мы должны переместить файл в другую папку.

Когда XML действителен, валидатор возвращает значение, и мы можем без проблем переместить файл. То же самое происходит, когда XML недействителен в соответствии со схемой.

Однако, если XML сформирован неправильно, валидатор выдает исключение, и когда мы пытаемся переместить файл, происходит сбой. Мы полагаем, что где-то в памяти все еще есть дескриптор, который удерживает файл. Мы попытались поместить System.gc() перед перемещением файла, и это решило проблему, но мы не можем использовать System.gc() в качестве решения.

Код выглядит так. У нас есть объект File, из которого мы создаем StreamSource. Затем StreamSource передается валидатору. Когда XML неправильно сформирован, он генерирует исключение SAXException. При обработке исключений мы используем метод .renameTo() для перемещения файла.

sc = new StreamSource(xmlFile);
validator.validate(sc);

В улове мы пытались

validator.reset();
validator=null;
sc=null;

но все же .renameTo() не может переместить файл. Если мы поставим System.gc() в ловушку, то ход будет успешным.

Может кто-нибудь просветить меня, как отсортировать это без System.gc()?

Мы используем JAXP и saxon-9.1.0.8 в качестве парсера.

Большое спасибо


person L4zl0w    schedule 01.08.2011    source источник


Ответы (5)


Попробуйте создать FileInputStream и передать его в StreamSource, а затем закрыть FileInputStream, когда закончите. Передав File, вы потеряли контроль над тем, как и когда закрывать дескриптор файла.

person Mike Q    schedule 01.08.2011

Когда вы устанавливаете sc = null, вы указываете сборщику мусора, что файл StreamSource больше не используется и что его можно собрать. Потоки закрываются в своем методе destroy(), поэтому, если они собираются сборщиком мусора, они будут закрыты и, следовательно, могут быть перемещены в системе Windows (у вас не будет этой проблемы в системе Unix).

Чтобы решить проблему, не вызывая GC вручную, просто вызовите sc.getInputStream().close() перед sc = null. В любом случае это хорошая практика.

Распространенным шаблоном является создание блока try .. finally вокруг любого использования дескриптора файла, например.

try {
    sc = new StreamSource(xmlFile);
    // check stuff
} finally {
    sc.getInputStream().close();
}
// move to the appropriate place

В Java 7 вы можете вместо этого использовать новый попробуйте с блоком ресурсов.

person stefan    schedule 01.08.2011
comment
Я не думаю, что у StreamSource есть метод close...? - person Mike Q; 01.08.2011
comment
Спасибо @stefan, но я не вижу метода .close() для StreamSource. - person L4zl0w; 01.08.2011
comment
Мне нравится, как я полностью отвечаю на вопрос, а также добавляю дополнительную полезную информацию, и меня минусуют за тривиальную педантичность в псевдокоде, выходящую за рамки исходного вопроса. Такое большое сообщество. - person stefan; 01.08.2011
comment
За вас проголосовали, потому что первый и второй предоставленные вами ответы / правки вводили в заблуждение / неверны, и это продолжает сбивать с толку, потому что вы все еще ссылаетесь на sc.close(), которого не существует. - person Mike Q; 01.08.2011

Попробуйте sc.getInputStream().close() в улове

person antlersoft    schedule 01.08.2011
comment
Спасибо, antlersoft, это имеет смысл, но я только что попробовал, и, к сожалению, это не работает. - person L4zl0w; 01.08.2011
comment
Стоит попробовать - вы уже пробовали решение Майка Кью? Это должно дать вам больше контроля над открытием файла... - person antlersoft; 01.08.2011

Все три уже данных ответа верны: вы должны закрыть базовый поток либо с помощью прямого вызова StramSource, либо получить поток и закрыть его, либо создать поток самостоятельно и закрыть его.

Тем не менее, я уже видел, как это происходит под окнами, по крайней мере, три года: даже если вы закроете поток, действительно каждый поток, если вы попытаетесь переместить или удалить файл, он вызовет исключение ... если только ... вы явно вызываете System.gc().

Однако, поскольку System.gc() не является обязательным для JVM для фактического выполнения цикла сборки мусора, и поскольку даже если это была JVM, она не обязана удалять все возможные объекты мусора, у вас нет реального способа убедиться, что файл можно удалить "сейчас".

У меня нет четкого объяснения, я могу только представить, что, вероятно, реализация java.io для Windows каким-то образом кэширует дескриптор файла и не закрывает его, пока дескриптор не будет собран мусором.

Сообщалось, но я не подтвердил это, что java.nio не подвержен такому поведению, потому что он имеет более низкий уровень контроля над файловыми дескрипторами.

Решение, которое я использовал в прошлом, но которое довольно хакерское, заключалось в следующем:

  1. Поместите файлы для удаления в «список»
  2. Пусть фоновый поток периодически проверяет этот список, вызывает System.gc и пытается удалить эти файлы.
  3. Удалите из списка те файлы, которые успели удалить, и оставьте там те, которые еще не готовы.

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

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

Это ошибка JRE, которую я нахожу самой раздражающей и глупой, и я не могу поверить, что она все еще существует, но, к сожалению, я столкнулся с ней всего месяц назад в Windows Vista с последней установленной JRE.

person Simone Gianni    schedule 01.08.2011
comment
Спасибо SimoneGianni, это хороший ответ. Я попытался запустить код в Unix, и он сразу заработал без каких-либо изменений. - person L4zl0w; 01.08.2011
comment
Да, проблема только в винде. В Unix я не знаю, отличается ли реализация JVM, в любом случае в Unix вы всегда можете удалить файл, даже если другой процесс читает или записывает его, так что это не имеет большого значения. - person Simone Gianni; 01.08.2011

Довольно старый, но некоторые люди все еще могут найти этот вопрос.

  1. Я использовал Oracle Java 1.8.0_77.
  2. Проблема возникает в Windows, а не в Linux.
  3. Создается впечатление, что экземпляр StreamSource с File автоматически выделяет и освобождает файловый ресурс при обработке валидатором или преобразователем. (getInputStream() возвращает null)
  4. В Windows перемещение файла на место исходного файла (удаление исходного файла) после обработки невозможно.

Решение/обходной путь: переместите файл, используя

Files.move(from.toPath(), to.toPath(), REPLACE_EXISTING, ATOMIC_MOVE);

Использование ATOMIC_MOVE здесь является критическим моментом. Какой бы ни была причина, она как-то связана с раздражающим поведением блокировки файлов Windows.

person Adam    schedule 06.04.2016