Подтвердить что ты не робот

Как получить список каталогов БЫСТРО в Java?

Предположим, что очень простая программа, в которой перечислены все подкаталоги данного каталога. Звучит достаточно просто? Кроме того, единственный способ перечислить все подкаталоги в Java - это использовать FilenameFilter в сочетании с File.list().

Это работает для тривиального случая, но когда в папке сказано 150 000 файлов и 2 подпапки, он глупо ждет там в течение 45 секунд, итерируя все файлы и тестируя файл file.isDirectory(). Есть ли лучший способ перечислить подкаталоги?


PS. Извините, пожалуйста, сохраните лекции о том, что в одном каталоге слишком много файлов. Наша живая среда имеет это как часть требования.

4b9b3361

Ответ 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;
}