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

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

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


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


person erotsppa    schedule 23.06.2009    source источник
comment
Я бы постарался не попасть в такую ситуацию в первую очередь. Наличие огромного количества файлов в каталоге может замедлить любое количество операций файловой системы.   -  person Jon Skeet    schedule 24.06.2009
comment
Извините, в нашей живой среде это является частью требования.   -  person erotsppa    schedule 24.06.2009
comment
Большинству другого кода в любом случае пришлось бы делать что-то подобное. Кстати, цикл из 150000 итераций не занимает 45 секунд. Это IO, который замедляет работу.   -  person nos    schedule 24.06.2009
comment
NIO2 (Java7) решит эту проблему, используя ленивый итератор для списка каталогов.   -  person dfa    schedule 24.06.2009
comment
@dfa Вам все равно придется просмотреть весь список файлов, чтобы убедиться, что вы нашли все подкаталоги, так что это действительно поможет, за исключением того, что одновременно создается несколько строк меньше?   -  person Hardwareguy    schedule 24.06.2009
comment
На какой системе это основано? И что еще более важно: какая файловая система?   -  person Emil H    schedule 24.06.2009
comment
@Hardwareguy нет, это лень. Вы не ждете 45 секунд, чтобы получить доступ к первой записи...   -  person dfa    schedule 24.06.2009
comment
@dfa, но, поскольку вам в конечном итоге придется получить к ним доступ, не будете ли вы по-прежнему выполнять тот же объем работы на диске?   -  person Hardwareguy    schedule 24.06.2009
comment
@Hardwareguy, вы не тратите впустую 150 000 элементов массива, но выполняете тот же объем работы над корпусом (в худшем случае)   -  person dfa    schedule 24.06.2009
comment
@dfa есть только худший случай, потому что он не узнает, получил ли он все подкаталоги, пока не проверит каждый файл   -  person Hardwareguy    schedule 24.06.2009
comment
@erotsppa - были ли ответы полезными? Хорошим тоном считается принять один из них, который оказался наиболее полезным.   -  person DVK    schedule 08.10.2009
comment
Вас может заинтересовать решение, которое я нашел для своей проблемы, очень похожей на вашу: stackoverflow.com/questions/13378382/   -  person gaborous    schedule 21.10.2013


Ответы (14)


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

Если вам по какой-то причине нужно хранить все файлы в одном каталоге, я думаю, вам придется поддерживать свой собственный кеш. Это можно сделать с помощью локальной базы данных, такой как sqlite, HeidiSQL или HSQL. Если вам нужна максимальная производительность, используйте java TreeSet и кэшируйте его в памяти. Это означает, по крайней мере, что вам придется реже читать каталог, и, возможно, это можно сделать в фоновом режиме. Вы можете уменьшить потребность в обновлении списка еще больше, используя собственный API уведомлений об обновлении файлов вашей системы (inotify в Linux), чтобы подписаться на изменения в каталоге.

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

images/[id - (id % 1000000)]/[id - (id % 1000)]/[id].jpg

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

person Emil H    schedule 23.06.2009
comment
Я бы предпочел поддерживать какой-то индекс (память / БД) вместо того, чтобы делать ввод-вывод каждый раз, когда я хочу перечислить файлы - person Ryan Fernandes; 03.07.2009

Знаете ли вы конечный список возможных имен подкаталогов? Если это так, используйте цикл по всем возможным именам и проверьте существование каталога.

В противном случае вы не сможете получить ТОЛЬКО имена каталогов в большинстве базовых ОС (например, в Unix список каталогов просто читает содержимое файла «каталога», поэтому нет возможности быстро найти «только каталоги», не перечисляя все файлы).

Однако в NIO.2 в Java7 (см. http://java.sun.com/developer/technicalArticles/javase/nio/#3 ), есть способ иметь список каталогов потоковой передачи, чтобы вы не получали полный массив файловых элементов, загромождающих вашу память/сеть.

person DVK    schedule 23.06.2009
comment
Даже если бы вышла версия 1.7, разве вам не пришлось бы проходить весь поток, чтобы увидеть, есть ли у вас все подкаталоги, так что это всего лишь крошечная оптимизация памяти? - person Hardwareguy; 24.06.2009
comment
Я предполагаю (из-за отсутствия точной документации), что потоковая передача позволит избежать хранения в памяти уже итерированных вещей. - person DVK; 24.06.2009
comment
Использование потоковой передачи nio повышает производительность. См. этот ответ с примерами stackoverflow.com/questions/31706058/ - person Luke; 14.05.2019

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

Во-первых: каталоги хранятся в файловой системе; думайте о них как о файлах, потому что это именно то, чем они являются. Когда вы перебираете каталог, вы должны считывать эти блоки с диска. Для каждой записи каталога потребуется достаточно места для хранения имени файла, разрешений и информации о том, где этот файл находится на диске.

Второе: каталоги не хранятся в каком-либо внутреннем порядке (по крайней мере, не в тех файловых системах, где я работал с файлами каталогов). Если у вас есть 150 000 записей и 2 подкаталога, эти 2 ссылки на подкаталоги могут быть где угодно в пределах 150 000. Вы должны повторить, чтобы найти их, нет никакого способа обойти это.

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

Альтернативой является создание многоуровневой структуры каталогов. Если вы посмотрите на коммерческие веб-сайты, вы увидите такие URL-адреса, как /1/150/15023.html — это предназначено для уменьшения количества файлов в каталоге. Думайте об этом как об индексе BTree в базе данных.

Конечно, вы можете скрыть эту структуру: вы можете создать уровень абстракции файловой системы, который принимает имена файлов и автоматически создает дерево каталогов, в котором эти имена файлов можно найти.

person kdgregory    schedule 23.06.2009
comment
Так меня заминусовали, потому что я дал неверный ответ (и если да, поправьте меня) или потому что я дал ответ, который вам не понравился? - person kdgregory; 24.06.2009
comment
Я не минусовал вас, но в вопросе довольно ясно, что он спрашивает; помочь быстро найти каталог, а не помочь организовать его файлы. - person Hardwareguy; 24.06.2009
comment
Я не тот, кто отрицал это, но вы делаете много заявлений о внутренней работе файловых систем без каких-либо ссылок и даже не зная, какая файловая система на самом деле используется. Это заставляет меня немного скептически относиться к правильности вашего поста, хотя я бы хотел оказаться неправым. - person Emil H; 24.06.2009
comment
@hardwareguy - Смысл моего сообщения заключался в том, что вы платите штрафы за ввод-вывод, имея большие структуры каталогов. - person kdgregory; 24.06.2009
comment
@emil - все структуры каталогов имеют схожие характеристики: они поддерживают список файлов. Некоторые ОС могут иметь специальный системный вызов для извлечения всех подкаталогов; Я никогда не видел ни одного для Unix или Windows, но системные вызовы для обеих систем выросли за пределы того, что я даже пытаюсь отслеживать. Как я уже отметил в своем комментарии, я бы предпочел реальный пример, а не анонимное понижение. Несмотря на это, он не доступен через Java API. - person kdgregory; 24.06.2009
comment
Что касается ссылок, вот одна для EXT2, которая (вместе с родственной EXT3) является одной из наиболее распространенных файловых систем в Linux: nongnu.org/ext2-doc/ext2.html#DIRECTORY — это был лучший результат Google, но я не могу подтвердить полномочия автора. Лично я не работал с данными каталогов со времен Linux SVR3. - person kdgregory; 24.06.2009
comment
Я думаю, что это все еще полезно, несмотря на то, что я не отвечаю на конкретный вопрос. Это делает шаг назад и дает некоторые сведения о том, почему запросы к каталогу медленные, и некоторые решения. Иногда ответ на вопрос заключается в том, чтобы спросить, задаете ли вы правильный вопрос (извините, если это звучит немного «мета») - person Brian Agnew; 24.06.2009
comment
s/Linux/Unix/ (и еще несколько символов, чтобы я мог ввести этот комментарий) - person kdgregory; 24.06.2009

Ключевой проблемой может быть функция File.isDirectory(), вызываемая в цикле.

File.isDirectory() может быть очень медленным. Я видел, как NFS требуется 10 секунд для обработки каталога с 200 файлами.

Если вы можете во что бы то ни стало предотвратить вызовы File.isDirectory() (например, проверить наличие расширения, каталог без расширения ==), вы можете значительно улучшить производительность.

В противном случае я бы предложил сделать JNA/JNI/написать собственный скрипт, который сделает это за вас.

Библиотека jCifs позволяет более эффективно управлять общими сетевыми ресурсами Windows. Я не знаю библиотеки, которая сделала бы это для других сетевых файловых систем.

person Roman Zenka    schedule 06.11.2009
comment
Каталоги могут иметь расширение. Файлы могут не иметь расширений. Так что ваш ответ не продолжается. - person BalusC; 06.11.2009
comment
@BalusC Да. Но иногда у вас есть контроль над именами — например, вы знаете, что файлы — это изображения с расширением из заданного набора, а каталоги всегда создаются без точки. Если это так, вы можете значительно ускорить процесс. - person Roman Zenka; 23.08.2011

Я столкнулся с подобным вопросом при отладке производительности в приложении 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;
}
person Xtra Coder    schedule 14.09.2016

Вы могли бы взломать его, если бы все 150 000 файлов (или значительное их количество) имели аналогичное соглашение об именах, например:

*.jpg
*Out.txt

и на самом деле создавайте файловые объекты только для тех, в которых вы не уверены, что это папка.

person Hardwareguy    schedule 23.06.2009
comment
Это не поможет, не так ли? Вместо того, чтобы тестировать каждый файл в FilenameFilter для isDirectory(), я бы проверил isNameSimilarTo(*.jpg)? - person erotsppa; 24.06.2009
comment
Вы будете выполнять некоторые строковые операции, которые, хотя и небыстры, должны быть быстрее, чем создание файловых объектов размером 150 КБ и вызов .isdirectory. Вы должны были бы взять некоторое время, чтобы увидеть, где реальное замедление. - person Hardwareguy; 24.06.2009
comment
Это фактически улучшило производительность в моем случае с 10 секунд до 1 секунды. В моем случае у меня было 1000 файлов и 10 каталогов на сетевом диске. Использование FilenameFilter и пропуск new File().isDirectory для всех файлов, оканчивающихся на .jpg, помогло! - person Thomas Jacob; 23.02.2020

Я не знаю, съедят ли его накладные расходы на 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 означает возврат полного пути от корня
person lavinio    schedule 23.06.2009
comment
Вы даже можете сохранить процесс cmd.exe и передать ему команду для каждого каталога, который вы хотите найти. - person finnw; 06.11.2009

если ваша ОС «стабильна», попробуйте JNA:

все это «потоковое API». Они не заставляют вас выделять список/массив 150k перед началом поиска. ИМХО это большое преимущество в вашем сценарии.

person dfa    schedule 23.06.2009

Вот нестандартное решение, вообще лишенное каких-либо испытаний. Это также зависит от наличия файловой системы, поддерживающей символические ссылки. Это не решение для Java. Я подозреваю, что ваша проблема связана с файловой системой/ОС, а не с Java.

Можно ли создать параллельную структуру каталогов с подкаталогами на основе начальных букв имен файлов, а затем символически связать с реальными файлами? Иллюстрация

/symlinks/a/b/cde

будет ссылаться на

/realfiles/abcde

(где /realfiles — это место, где находятся ваши 150 000 файлов)

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

person Brian Agnew    schedule 23.06.2009

также существует рекурсивное параллельное сканирование по адресу http://blogs.oracle.com/adventures/entry/fast_directory_scanning. По существу братья и сестры обрабатываются параллельно. Есть и обнадеживающие тесты производительности.

person dfa    schedule 23.06.2009

Возможно, вы могли бы написать программу поиска в каталогах на C#/C/C++ и использовать JNI, чтобы перевести ее на Java. Не знаю, улучшит ли это производительность или нет.

person Nick    schedule 23.06.2009
comment
Это не проблема Java, доступ к диску медленный, независимо от того, какой язык вы используете. - person Hardwareguy; 24.06.2009
comment
@Ник: Это не помогает. Java уже использует собственные библиотеки для доступа к хост-файлам. - person OscarRyz; 24.06.2009
comment
@Hardwareguy: Да, диск медленный. Но вы можете значительно ухудшить жизнь, выполняя больше операций ввода-вывода, чем необходимо. В среде C/UNIX я могу выполнить последовательное чтение всего каталога и просмотреть результаты, чтобы найти каталоги. Менее эффективное решение будет выполнять отдельный ввод-вывод для каждой записи, чтобы выяснить, является ли она каталогом. Таким образом, эффективность здесь зависит от того, что на самом деле делает Java. - person Keith Smith; 24.06.2009
comment
Если вы полностью напишите обход каталога в собственном коде, вы вернете время, потерянное на постоянные переходы между управляемым и собственным кодом решения Java. - person akarnokd; 24.06.2009

Ну, либо JNI, либо, если вы говорите, что ваше развертывание является постоянным, просто запустите «dir» в Windows или «ls» в * nixes с соответствующими флагами для вывода списка только каталогов (Runtime.exec())

person Yoni Roit    schedule 23.06.2009

В этом случае вы можете попробовать какое-то решение JNA - обходчик каталогов, зависящий от платформы (FindFirst, FindNext в Windows) с возможностью некоторого шаблона итерации. Кроме того, Java 7 будет иметь гораздо лучшую поддержку файловой системы, стоит проверить спецификации (я не помню никаких подробностей).

Редактировать: Идея: один из вариантов — скрыть от глаз пользователя медлительность листинга каталога. В клиентском приложении вы можете использовать некоторую анимацию, пока листинг работает, чтобы отвлечь пользователя. На самом деле зависит от того, что еще делает ваше приложение помимо листинга.

person akarnokd    schedule 23.06.2009
comment
Это не решит основную проблему медленного доступа к файловой системе для папок с таким количеством файлов в них. Это не проблема java. - person Hardwareguy; 24.06.2009
comment
FindFirst позволяет явно фильтровать каталоги. Я не знаю о readdir. Я думаю, что большинство современных файловых систем могут использовать это. - person akarnokd; 24.06.2009

По состоянию на 2020 год DirectoryStream кажется быстрее, чем использование File.listFiles() и проверка каждого файла с помощью isDirectory().

Я узнал ответ отсюда:

https://www.baeldung.com/java-list-directory-files

Я использую Java 1.8 в Windows 10.

person user1324109    schedule 26.05.2020
comment
и здесь: stackoverflow.com/questions/5125242/ Я думаю, что другая страница StackOverflow более актуальна и содержит больше информации. - person user1324109; 27.05.2020