Как получить список каталогов БЫСТРО в Java?
Предположим, что очень простая программа, в которой перечислены все подкаталоги данного каталога. Звучит достаточно просто? Кроме того, единственный способ перечислить все подкаталоги в Java - это использовать FilenameFilter в сочетании с File.list().
Это работает для тривиального случая, но когда в папке сказано 150 000 файлов и 2 подпапки, он глупо ждет там в течение 45 секунд, итерируя все файлы и тестируя файл file.isDirectory(). Есть ли лучший способ перечислить подкаталоги?
PS. Извините, пожалуйста, сохраните лекции о том, что в одном каталоге слишком много файлов. Наша живая среда имеет это как часть требования.
Ответы
Ответ 1
Как уже упоминалось, это в основном аппаратная проблема. Доступ к диску всегда медленный, и большинство файловых систем не предназначены для обработки каталогов с таким количеством файлов.
Если вы по какой-то причине должны хранить все файлы в одном каталоге, я думаю, вам нужно будет поддерживать свой собственный кеш. Это можно сделать с помощью локальной базы данных, такой как sqlite, HeidiSQL или HSQL. Если вы хотите получить максимальную производительность, используйте java TreeSet и кешируйте его в памяти. Это означает, по крайней мере, что вам придется читать каталог менее часто, и это можно сделать в фоновом режиме. Вы могли бы уменьшить необходимость обновлять список еще больше, используя собственный API уведомлений об обновлении файла собственных систем (inotify on linux), чтобы подписаться на изменения в каталоге.
Это не кажется вам возможным, но однажды я решил подобную проблему, "хешируя" файлы в подкаталоги. В моем случае задача состояла в том, чтобы хранить несколько миллионов изображений с числовыми идентификаторами. Я построил структуру каталогов следующим образом:
images/[id - (id % 1000000)]/[id - (id % 1000)]/[id].jpg
Это хорошо сработало для нас, и это решение, которое я бы рекомендовал. Вы могли бы сделать что-то похожее на альфа-числовые имена файлов, просто взяв первые две буквы имени файла, а затем следующие две буквы. Я сделал это тоже один раз, и он тоже выполнил эту работу.
Ответ 2
Знаете ли вы конечный список возможных имен подкаталогов? Если это так, используйте цикл для всех возможных имен и проверьте существование каталога.
В противном случае вы не можете получать ТОЛЬКО имена каталогов в большинстве базовых ОС (например, в Unix, список каталогов - это просто чтение содержимого файла "directory", поэтому нет возможности быстро найти "просто каталоги", не указав все файлы).
Однако в NIO.2 в Java7 (см. http://java.sun.com/developer/technicalArticles/javase/nio/#3), есть способ получить список потоковых каталогов, t получить полный массив файловых элементов, загромождающих вашу память/сеть.
Ответ 3
На самом деле есть причина, по которой вы получили лекции: это правильный ответ на вашу проблему. Вот фон, чтобы, возможно, вы могли внести некоторые изменения в свою живую среду.
Сначала: каталоги хранятся в файловой системе; думайте о них как о файлах, потому что это именно то, что они есть. Когда вы итерации через каталог, вы должны прочитать эти блоки с диска. Для каждой записи в каталоге требуется достаточно места для хранения имени файла и разрешений, а также информации о том, где этот файл находится на диске.
Во-вторых: каталоги не сохраняются с каким-либо внутренним упорядочением (по крайней мере, не в файловых системах, где я работал с файлами каталога). Если у вас 150 000 записей и 2 подкаталога, эти 2 ссылки на подкаталоги могут быть в пределах 150 000. Вы должны итерации, чтобы найти их, нет никакого способа обойти это.
Итак, скажем, что вы не можете избежать большого каталога. Единственный реальный вариант - попытаться сохранить блоки, содержащие файл каталога, в кеше в памяти, чтобы вы не попадали на диск при каждом доступе к ним. Вы можете добиться этого, регулярно повторяя каталог в фоновом потоке, но это приведет к чрезмерной нагрузке на ваши диски и помешает другим процессам. Кроме того, вы можете сканировать один раз и отслеживать результаты.
Альтернативой является создание многоуровневой структуры каталогов. Если вы посмотрите на коммерческие веб-сайты, вы увидите URL-адреса, такие как /1/150/15023.html - это означает, что количество файлов в каталоге меньше. Подумайте об этом как о индексе BTree в базе данных.
Конечно, вы можете скрыть эту структуру: вы можете создать слой абстракции файловой системы, который принимает имена файлов и автоматически генерирует дерево каталогов, где эти имена файлов могут быть найдены.
Ответ 4
Я не знаю, хватит ли накладных расходов на обрезку cmd.exe
, но одна возможность может быть примерно такой:
...
Runtime r = Runtime.getRuntime();
Process p = r.exec("cmd.exe /k dir /s/b/ad C:\\folder");
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
for (;;) {
String d = br.readLine();
if (d == null)
break;
System.out.println(d);
}
...
- /s означает поиск подкаталогов
- /ad означает только каталоги возврата
- /b означает возвращение полного пути из корня
Ответ 5
Вы можете взломать его, если все файлы 150k (или значительное их число) имеют аналогичное соглашение об именах, например:
*.jpg
*Out.txt
и только на самом деле создавать объекты файлов для тех, которые вы не уверены в том, что являетесь папкой.
Ответ 6
Ключевой проблемой может быть функция File.isDirectory(), вызываемая в цикле.
File.isDirectory() может быть очень медленным. Я видел, что NFS занимает 10 секунд, чтобы обрабатывать каталог 200 файлов.
Если вы можете во что бы то ни стало предотвратить вызовы File.isDirectory() (например, тест для расширения, каталог с расширением ==), вы могли бы значительно улучшить производительность.
В противном случае я бы предложил сделать JNA/JNI/записать родной script, который сделает это для вас.
Библиотека jCifs позволяет более эффективно управлять сетевыми ресурсами Windows. Я не знаю о библиотеке, которая будет делать это для других сетевых файловых систем.
Ответ 7
если ваша ОС "стабильная", попробуйте JNA:
все это "потоковый API". Они не заставляют вас выделять список/массив 150k перед началом поиска. ИМХО это большое преимущество в вашем сценарии.
Ответ 8
также существует рекурсивное параллельное сканирование в http://blogs.oracle.com/adventures/entry/fast_directory_scanning. По существу братья и сестры обрабатываются параллельно. Там также поощряются тесты производительности.
Ответ 9
Здесь нестандартное решение, и вообще никаких испытаний. Это также зависит от наличия файловой системы, поддерживающей символические ссылки. Это не решение Java. Я подозреваю, что ваша проблема связана с файловой системой и ОС, а не с Java.
Можно ли создать параллельную структуру каталогов с подкаталогами на основе начальных букв имен файлов, а затем символически ссылаться на реальные файлы? Иллюстрация
/symlinks/a/b/cde
будет ссылаться на
/realfiles/abcde
(где/realfiles находится там, где находятся ваши 150 000 файлов)
Вам нужно будет создать и поддерживать эту структуру каталогов, и у меня недостаточно информации, чтобы определить, насколько это практично. Но выше было бы создать быстрый (er) индекс в ваш неиерархический (и медленный) каталог.
Ответ 10
Возможно, вы могли бы написать программу поиска каталогов в С#/C/С++ и использовать JNI для ее получения на Java. Не знаю, улучшит ли это производительность или нет.
Ответ 11
В этом случае вы можете попробовать некоторое решение JNA - трассировщик каталогов, зависящий от платформы (FindFirst, FindNext в Windows) с возможностью некоторого шаблона итерации. Кроме того, Java 7 будет иметь гораздо лучшую поддержку файловой системы, стоит проверить спецификации (я не помню никаких особенностей).
Изменить: Идея: один из вариантов заключается в том, чтобы скрыть медлительность списка каталогов из глаз пользователя. В приложении на стороне клиента вы можете использовать некоторую анимацию, пока список работает, чтобы отвлечь пользователя. Фактически, зависит от того, что еще делает ваше приложение рядом с листингом.
Ответ 12
Ну, либо JNI, либо, если вы говорите, что ваше развертывание постоянное, просто запустите "dir" в Windows или "ls" на * nixes, с соответствующими флагами, чтобы перечислять только каталоги (Runtime.exec())
Ответ 13
Я столкнулся с похожим вопросом при отладке производительности в приложении Java, перечисляющем большое количество файлов. Он использует старый подход
for (File f : new File("C:\\").listFiles()) {
if (f.isDirectory()) {
continue;
}
}
И кажется, что каждый f.isDirectory() является вызовом в родную FileSsystem, которая, по крайней мере, на NTFS, работает очень медленно. Java7 NIO имеет дополнительный API, но не все методы там хороши. Я просто предоставил результат теста JMH здесь.
Benchmark Mode Cnt Score Error Units
MyBenchmark.dir_listFiles avgt 5 0.437 ? 0.064 s/op
MyBenchmark.path_find avgt 5 0.046 ? 0.001 s/op
MyBenchmark.path_walkTree avgt 5 1.702 ? 0.047 s/op
Число исходит от выполнения этого кода:
java -jar target/benchmarks.jar -bm avgt -f 1 -wi 5 -i 5 -t 1
static final String testDir = "C:/Sdk/Ide/NetBeans/src/dev/src/";
static final int nCycles = 50;
public static class Counter {
int countOfFiles;
int countOfFolders;
}
@Benchmark
public List<File> dir_listFiles() {
List<File> files = new ArrayList<>(1000);
for( int i = 0; i < nCycles; i++ ) {
File dir = new File(testDir);
files.clear();
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
continue;
}
files.add(f);
}
}
return files;
}
@Benchmark
public List<Path> path_walkTree() throws Exception {
final List<Path> files = new ArrayList<>(1000);
for( int i = 0; i < nCycles; i++ ) {
Path dir = Paths.get(testDir);
files.clear();
Files.walkFileTree(dir, new SimpleFileVisitor<Path> () {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes arg1) throws IOException {
files.add(path);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes arg1)
throws IOException {
return path == dir ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE;
}
});
}
return files;
}
@Benchmark
public List<Path> path_find() throws Exception {
final List<Path> files = new ArrayList<>(1000);
for( int i = 0; i < nCycles; i++ ) {
Path dir = Paths.get(testDir);
files.clear();
files.addAll(Files.find(dir, 1, (path, attrs)
-> true /*!attrs.isDirectory()*/).collect(Collectors.toList()));
}
return files;
}