Как быстро найти добавленные/удаленные файлы?

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

Как только я сгенерировал индекс, есть ли быстрый способ узнать, какие файлы были добавлены или удалены на диске с момента последнего запуска?

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

Маленький бенчмарк

Я просто сделал небольшой ориентир. Запуск

dir /b /s M:\tests\  >c:\out.txt

Получает 0,9 секунды и дает мне всю необходимую мне информацию. Когда я использую Java-реализацию (как это), она занимает около 4,5 секунд. Любые идеи о том, как улучшить по крайней мере этот подход грубой силы?

Похожие сообщения: Как узнать, изменился ли подфайл каталога

Ответ 1

Я сделал это в своем инструменте MetaMake. Вот рецепт:

  • Если индекс пуст, добавьте корневой каталог в индекс с отметкой времени == dir.lastModified() - 1.
  • Найти все каталоги в индексе
  • Сравните временную метку каталога в индексе с именем из файловой системы. Это быстрая операция, так как у вас есть полный путь (нет сканирования всех файлов /dirs в рассматриваемом дереве).
  • Если временная метка изменилась, у вас есть изменение в этом каталоге. Повторно сканируйте его и обновите индекс.
  • Если вы столкнулись с отсутствующими каталогами на этом шаге, удалите поддерево из индекса
  • Если вы столкнулись с существующим каталогом, проигнорируйте его (будет проверен на шаге 2)
  • Если вы столкнулись с новым каталогом, добавьте его с отметкой timestamp == dir.lastModified() - 1. Убедитесь, что он рассматривается на шаге 2.

Это позволит вам эффективно замечать новые и удаленные файлы. Поскольку вы сканируете только известные пути на шаге 2, это будет очень эффективно. Файловые системы плохи при перечислении всех записей в каталоге, но они быстры, когда вы знаете точное имя.

Недостаток: вы не заметите измененные файлы. Поэтому, если вы редактируете файл, это не отразится на изменении каталога. Если вам нужна эта информация, вам также придется повторить алгоритм выше для узлов файла в вашем индексе. На этот раз вы можете игнорировать новые/удаленные файлы, поскольку они уже обновлены во время прогона по каталогам.

[EDIT] Зак отметил, что временных меток недостаточно. Мой ответ: просто нет другого способа сделать это. Понятие "размер" полностью undefined для каталогов и изменений от реализации до реализации. Нет API, в котором вы можете зарегистрировать "Я хочу получать уведомления о любых изменениях, сделанных чем-то в файловой системе". Есть API, которые работают, пока ваше приложение жив, но если оно останавливается или пропускает событие, вы не синхронизированы.

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

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

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

Ответ 2

Вы можете выпрыгнуть из java.

Вы можете просто использовать

dir /b /s /on M:\tests\  

the/on сортирует по имени

если вы передаете это вне out.txt

Затем выполните diff в последний раз, когда вы запустили этот файл либо в Java, либо в пакетном файле. Что-то вроде этого в Дос. Вам нужно будет получить инструмент diff, либо diff в cygwin, либо отличный http://gnuwin32.sourceforge.net/packages/diffutils.htm

dir /b /s /on m:\tests >new.txt
diff new.txt archive.txt >diffoutput.txt
del archive.txt
ren new.txt archive.txt

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

Ответ 3

К сожалению, нет стандартного способа прослушивания событий файловой системы в java. Это может быть в java7.

На данный момент вам нужно будет "java filesystem events" в google и выбрать пользовательскую реализацию, соответствующую вашей платформе.

Ответ 4

Один из способов ускорить работу - это просто перебрать каталоги и проверить последнее измененное время, чтобы увидеть, изменилось ли содержимое каталога с момента последнего индекса, и если они просто выполняют обычное сканирование в каталоге затем посмотрите, сможете ли вы найти, где все изменилось. Я не знаю, насколько переносимым это будет, но изменение иерархии распространяется на систему Linux (возможно, зависит от файловой системы), поэтому вы можете начать с корня и работать вниз, останавливаясь, когда вы попадаете в каталог, который hasn 'изменено

Ответ 5

Учитывая, что мы не хотим отслеживать события файловой системы, можем ли мы просто отслеживать (name,size,time,checksum) каждого файла? Вычисление контрольной суммы файла (или криптографического хэша, если хотите) будет узким местом. Вы можете просто вычислить его один раз в первом прогоне и повторно вычислить его только тогда, когда это необходимо впоследствии (например, когда файлы совпадают с тремя другими атрибутами). Конечно, нам не нужно беспокоиться об этом, если мы хотим отслеживать имена файлов, а не файлы.

Вы отмечаете, что ваша реализация Java (похожая на this) очень медленная по сравнению с "dir /s". Я думаю, есть две причины для этого:

Ответ 6

Подход даты файла может быть не лучшим. Например, если вы восстановите файл из резервной копии. Возможно, во время индексации вы можете сохранить хеш MD5 содержимого файла. Однако вам может потребоваться провести сравнительный анализ производительности, чтобы убедиться, что производительность приемлема.

Ответ 7

Я слышал, что эту задачу очень сложно сделать эффективно. Я уверен, что MS реализовала бы подобный инструмент для Windows, если бы это было легко, особенно в наши дни, поскольку HD: s растут и растут.

Ответ 8

Я не проверял реализацию или производительность, но commons-io имеет метод listFiles(). Возможно, стоит попробовать.

Ответ 9

Как насчет чего-то как это:

private static String execute( String command ) throws IOException  { 
    Process p = Runtime.getRuntime().exec( "cmd /c " + command );
    InputStream i = p.getInputStream();
    StringBuilder sb = new StringBuilder();
    for(  int c = 0 ; ( c =  i.read() ) > -1  ; ) {
        sb.append( ( char ) c );
    }
    i.close();
    return sb.toString();
}

(Там есть много места для улучшения, так как эта версия читает один char за раз: Вы можете выбрать лучшую версию здесь, чтобы быстрее читать поток)

И вы используете в качестве аргумента:

"dir /b /s M:\tests\"

Если это будет использоваться в запущенном приложении (скорее, и будет автономным приложением), вы можете сэкономить время "разогрева" JVM, примерно на 1 - 2 секунды в зависимости от вашего оборудования.

Вы можете попытаться понять, что это за последствия.

Ответ 10

Попробуйте использовать git. Программное обеспечение для управления версиями ориентировано на эту проблему, а git имеет хорошую репутацию за скорость; он специально разработан для быстрой работы с локальными файлами. 'git diff -name-status' доставит вам то, что вы хотите, я думаю.