Arhn - архитектура программирования

Список всех файлов из каталога рекурсивно с помощью Java

У меня есть эта функция, которая рекурсивно печатает имя всех файлов в каталоге. Проблема в том, что мой код очень медленный, потому что ему приходится обращаться к удаленному сетевому устройству на каждой итерации.

Мой план состоит в том, чтобы сначала загрузить все файлы из каталога рекурсивно, а затем просмотреть все файлы с регулярным выражением, чтобы отфильтровать все файлы, которые мне не нужны. У кого-нибудь есть лучшее предложение?

public static printFnames(String sDir){
  File[] faFiles = new File(sDir).listFiles();
  for(File file: faFiles){
    if(file.getName().matches("^(.*?)")){
      System.out.println(file.getAbsolutePath());
    }
    if(file.isDirectory()){
      printFnames(file.getAbsolutePath());
    }
  }
}

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

28.03.2010

  • ... какой вопрос? Вы просто ищете подтверждение того, что этот код будет работать? 29.03.2010
  • Нет, я знаю, что этот код работает, но он очень медленный и кажется глупым обращаться к файловой системе и получать содержимое для каждого подкаталога вместо того, чтобы получать все сразу. 29.03.2010
  • возможный дубликат рекурсивного списка файлов в Java 03.07.2013

Ответы:


1

Предполагая, что вы будете писать реальный производственный код, я предлагаю использовать решение для подобных вещей, которое уже было решено — Предварительный ввод-вывод Apache Commons, в частности FileUtils.listFiles(). Он обрабатывает вложенные каталоги, фильтры (на основе имени, времени модификации и т. д.).

Например, для вашего регулярного выражения:

Collection files = FileUtils.listFiles(
  dir, 
  new RegexFileFilter("^(.*?)"), 
  DirectoryFileFilter.DIRECTORY
);

Это будет рекурсивно искать файлы, соответствующие регулярному выражению ^(.*?), возвращая результаты в виде коллекции.

Стоит отметить, что это будет не быстрее, чем накатывать ваш собственный код, это делает то же самое - траллинг файловой системы в Java просто медленный. Разница в том, что в версии Apache Commons не будет ошибок.

28.03.2010
  • Я посмотрел туда, и оттуда я бы использовал commons.apache.org/io/api-release/index.html?org/apache/commons/, чтобы получить все файлы из каталога и подкаталогов, а затем выполнить поиск по файлам, чтобы они соответствовали моему регулярное выражение Или я ошибаюсь? 29.03.2010
  • Да, проблема в том, что сканирование папки занимает больше часа, и делать это каждый раз, когда я запускаю программу для проверки обновлений, очень раздражает. Было бы быстрее, если бы я написал эту часть программы на C, а остальную часть на Java, и если да, то была бы какая-то существенная разница? На данный момент я изменил код в строке if isdir и добавил, что каталог также должен соответствовать регулярному выражению, чтобы быть включенным в поиск. Я вижу, что в вашем примере написано DirectoryFileFilter.DIRECTORY, я думаю, у меня мог бы быть фильтр регулярных выражений. 29.03.2010
  • написание его с использованием собственных вызовов абсолютно ускорит его — FindFirstFile/FineNextFile позволяет вам запрашивать атрибуты файла без необходимости делать для него отдельный вызов — это может иметь серьезные последствия для сетей с более высокой задержкой. Подход Java к этому ужасно неэффективен. 31.03.2011
  • @hanzallah-afgan: И вопросу, и ответу более 5 лет. За прошедшее время было выпущено два основных выпуска Java, поэтому вам, возможно, не захочется исследовать новые функции, такие как Java 7 NIO. 03.06.2015
  • Да, но это тоже было неплохо. Кстати, вы тоже правы. 06.06.2015
  • @skaffman Как я могу адаптировать приведенный выше код для использования в отображаемых подкаталогах и файлах из текущего каталога? 16.11.2016
  • этот ответ очень устарел 21.11.2016
  • Используйте FileUtils только в том случае, если вы знаете и принимаете снижение производительности: github.com/brettryan/io- рекурсивные тесты. Собственные альтернативы java8 позволяют использовать краткую и более эффективную нотацию, например: Files.walk(Paths.get("/etc")).filter(Files::isRegularFile).collect(Collectors.toList()) 23.04.2017

  • 2

    В Java 8 это однострочный файл через Files.find() с произвольно большой глубиной (например, 999) и BasicFileAttributes из isRegularFile()

    public static printFnames(String sDir) {
        Files.find(Paths.get(sDir), 999, (p, bfa) -> bfa.isRegularFile()).forEach(System.out::println);
    }
    

    Чтобы добавить больше фильтрации, улучшите лямбду, например, все файлы jpg, измененные за последние 24 часа:

    (p, bfa) -> bfa.isRegularFile()
      && p.getFileName().toString().matches(".*\\.jpg")
      && bfa.lastModifiedTime().toMillis() > System.currentMillis() - 86400000
    
    02.11.2015
  • Я предлагаю всегда использовать те методы Files, которые возвращают Stream в блоках try-with-resources: иначе вы оставите ресурс открытым 21.06.2017
  • Разве терминальные операции не вызывают закрытие самого потока? 21.10.2020
  • @ Драгас, да. Мой потребитель — это всего лишь простой пример; в реальной жизни вы бы сделали что-то более полезное. 21.10.2020

  • 3

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

    Он использует класс Java 7 NIO Path.

    private List<String> getFileNames(List<String> fileNames, Path dir) {
        try(DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
            for (Path path : stream) {
                if(path.toFile().isDirectory()) {
                    getFileNames(fileNames, path);
                } else {
                    fileNames.add(path.toAbsolutePath().toString());
                    System.out.println(path.getFileName());
                }
            }
        } catch(IOException e) {
            e.printStackTrace();
        }
        return fileNames;
    } 
    
    20.06.2014

    4

    В Java 7 был введен более быстрый способ обхода дерева каталогов с помощью функций Paths и Files. Они намного быстрее, чем "старый" File способ.

    Это будет код для обхода и проверки имен путей с помощью регулярного выражения:

    public final void test() throws IOException, InterruptedException {
        final Path rootDir = Paths.get("path to your directory where the walk starts");
    
        // Walk thru mainDir directory
        Files.walkFileTree(rootDir, new FileVisitor<Path>() {
            // First (minor) speed up. Compile regular expression pattern only one time.
            private Pattern pattern = Pattern.compile("^(.*?)");
    
            @Override
            public FileVisitResult preVisitDirectory(Path path,
                    BasicFileAttributes atts) throws IOException {
    
                boolean matches = pattern.matcher(path.toString()).matches();
    
                // TODO: Put here your business logic when matches equals true/false
    
                return (matches)? FileVisitResult.CONTINUE:FileVisitResult.SKIP_SUBTREE;
            }
    
            @Override
            public FileVisitResult visitFile(Path path, BasicFileAttributes mainAtts)
                    throws IOException {
    
                boolean matches = pattern.matcher(path.toString()).matches();
    
                // TODO: Put here your business logic when matches equals true/false
    
                return FileVisitResult.CONTINUE;
            }
    
            @Override
            public FileVisitResult postVisitDirectory(Path path,
                    IOException exc) throws IOException {
                // TODO Auto-generated method stub
                return FileVisitResult.CONTINUE;
            }
    
            @Override
            public FileVisitResult visitFileFailed(Path path, IOException exc)
                    throws IOException {
                exc.printStackTrace();
    
                // If the root directory has failed it makes no sense to continue
                return path.equals(rootDir)? FileVisitResult.TERMINATE:FileVisitResult.CONTINUE;
            }
        });
    }
    
    14.08.2013
  • Хороший ответ :), есть также реализованный класс SimpleFileVisitor, если вам не нужны все реализованные функции, вы можете просто переопределить необходимые функции. 24.03.2014

  • 5

    Быстрый способ получить содержимое каталога с помощью Java 7 NIO:

    import java.nio.file.DirectoryStream;
    import java.nio.file.Files;
    import java.nio.file.FileSystems;
    import java.nio.file.Path;
    
    ...
    
    Path dir = FileSystems.getDefault().getPath( filePath );
    DirectoryStream<Path> stream = Files.newDirectoryStream( dir );
    for (Path path : stream) {
       System.out.println( path.getFileName() );
    }
    stream.close();
    
    18.03.2013
  • Хорошо, но получает файлы только для одного каталога. Если вы хотите увидеть все подкаталоги, посмотрите мой альтернативный ответ. 20.06.2014
  • Files.newDirectoryStream может вызвать исключение IOException. Я предлагаю обернуть эту строку в оператор try-with-java7, чтобы поток всегда был закрыт для вас (исключение или нет, без необходимости использования finally). См. также здесь: stackoverflow.com/questions/ 17739362/ 17.09.2014

  • 6

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

    Основная проблема заключается в том, что Java выполняет собственный системный вызов для каждого отдельного файла. На интерфейсе с малой задержкой это не так уж важно, но в сети даже с умеренной задержкой это действительно складывается. Если вы профилируете свой алгоритм выше, вы обнаружите, что основная часть времени тратится на надоедливый вызов isDirectory() - это потому, что вы выполняете круговое путешествие для каждого отдельного вызова isDirectory(). Большинство современных операционных систем могут предоставить такую ​​информацию, когда изначально запрашивался список файлов/папок (в отличие от запроса каждого отдельного пути к файлу для его свойств).

    Если вы не можете дождаться JDK7, одной из стратегий устранения этой задержки является использование многопоточности и использование ExecutorService с максимальным числом потоков для выполнения вашей рекурсии. Это не очень хорошо (вам придется иметь дело с блокировкой ваших структур выходных данных), но это будет намного быстрее, чем выполнение этого однопоточного.

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

    29.03.2010
  • Теперь есть Java 7, поэтому пример того, как это сделать в Java 7, будет полезен. Или хотя бы ссылку. Или имя класса для поиска в Google. — это ведь «stackoverflow», а не «теоретическая cs» ;-) . 19.01.2012
  • хорошо, давайте посмотрим... Мой первоначальный пост был в марте 2010 года... Сейчас январь 2012 года... И я только что проверил историю инвентаризации своего оборудования, и я не вижу, чтобы у меня была машина времени в марте 10 года, поэтому я думаю, что я, вероятно, вправе ответить, не приводя явного примера ;-) 01.02.2012
  • @Martin Это документы, которые вам нужны. 10.05.2012

  • 7

    это будет работать нормально... и рекурсивно

    File root = new File("ROOT PATH");
    for ( File file : root.listFiles())
    {
        getFilesRecursive(file);
    }
    
    
    private static void getFilesRecursive(File pFile)
    {
        for(File files : pFile.listFiles())
        {
            if(files.isDirectory())
            {
                getFilesRecursive(files);
            }
            else
            {
                // do your thing 
                // you can either save in HashMap and use it as
                // per your requirement
            }
        }
    }
    
    19.02.2015
  • Хороший ответ, если вы хотите что-то, что работает с Java ‹7. 13.04.2016

  • 8

    Мне лично нравится эта версия FileUtils. Вот пример, который находит все файлы mp3 или flac в каталоге или любом из его подкаталогов:

    String[] types = {"mp3", "flac"};
    Collection<File> files2 = FileUtils.listFiles(/path/to/your/dir, types , true);
    
    20.05.2015

    9

    Это будет работать нормально

    public void displayAll(File path){      
        if(path.isFile()){
            System.out.println(path.getName());
        }else{
            System.out.println(path.getName());         
            File files[] = path.listFiles();
            for(File dirOrFile: files){
                displayAll(dirOrFile);
            }
        }
    }
    

    30.04.2015
  • Добро пожаловать в StackOverflow Mam, не могли бы вы уточнить, как ваш ответ является улучшением или альтернативой многим существующим ответам? 30.04.2015

  • 10

    Ява 8

    public static void main(String[] args) throws IOException {
    
            Path start = Paths.get("C:\\data\\");
            try (Stream<Path> stream = Files.walk(start, Integer.MAX_VALUE)) {
                List<String> collect = stream
                    .map(String::valueOf)
                    .sorted()
                    .collect(Collectors.toList());
    
                collect.forEach(System.out::println);
            }
    
    
        }
    
    29.05.2018

    11

    Эта функция, вероятно, перечислит все имена файлов и их пути из своего каталога и его подкаталогов.

    public void listFile(String pathname) {
        File f = new File(pathname);
        File[] listfiles = f.listFiles();
        for (int i = 0; i < listfiles.length; i++) {
            if (listfiles[i].isDirectory()) {
                File[] internalFile = listfiles[i].listFiles();
                for (int j = 0; j < internalFile.length; j++) {
                    System.out.println(internalFile[j]);
                    if (internalFile[j].isDirectory()) {
                        String name = internalFile[j].getAbsolutePath();
                        listFile(name);
                    }
    
                }
            } else {
                System.out.println(listfiles[i]);
            }
    
        }
    
    }
    
    08.03.2013
  • В этом примере не учитывается тот факт, что метод listFiles() может и будет возвращать значение null. docs.oracle.com/javase/ 7/docs/api/java/io/File.html#listFiles() 25.09.2013

  • 12

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

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

    28.03.2010
  • Дело в том, что я хочу загрузить все файлы определенного типа с определенным форматом имени в библиотеку, которая предоставляется пользователю, и каждый раз, когда приложение запускается, библиотека должна обновляться, но обновление библиотеки занимает вечность. Единственное решение, которое у меня есть, - это запустить обновление в фоновом режиме, но все равно раздражает, что загрузка всех новых файлов занимает так много времени. Должен быть лучший способ сделать это. Или, по крайней мере, лучший способ обновить базу данных. Для него глупо просматривать все файлы, которые он уже просматривал один раз. Есть ли способ только быстро найти обновления. 29.03.2010
  • @Hultner: Java 7 будет включать средство для получения уведомлений об обновлениях файловой системы, но это все равно будет работать только во время работы приложения, поэтому, если вы не хотите, чтобы фоновая служба работала все время, это не поможет. Как описывает Кевин, могут возникнуть особые проблемы с общими сетевыми ресурсами, но пока вы зависите от сканирования всего дерева каталогов, на самом деле нет лучшего способа. 29.03.2010
  • Возможно, вы могли бы создать несколько индексных файлов. Если есть способ проверить размер каталога, вы можете просто сканировать новые файлы при изменении размера. 07.02.2011
  • @James: нет возможности проверить размер каталога. Размер каталога получается путем получения размера каждого файла и их суммирования во всех известных мне файловых системах. Собственно, вопрос, каков размер этого каталога? даже не обязательно имеет смысл, если учесть жесткие ссылки. 07.02.2011
  • Ты прав. Я все еще чувствую, что некоторое кэширование и/или снятие отпечатков могут ускорить процесс. 07.02.2011

  • 13

    Просто чтобы вы знали, что isDirectory() — довольно медленный метод. Я нахожу это довольно медленным в моем файловом браузере. Я буду искать библиотеку, чтобы заменить ее собственным кодом.

    07.02.2011

    14

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

    09.12.2011

    15

    В Guava вам не нужно ждать, пока вам вернут коллекцию, но вы можете фактически перебирать файлы. Легко представить интерфейс IDoSomethingWithThisFile в сигнатуре функции ниже:

    public static void collectFilesInDir(File dir) {
        TreeTraverser<File> traverser = Files.fileTreeTraverser();
        FluentIterable<File> filesInPostOrder = traverser.preOrderTraversal(dir);
        for (File f: filesInPostOrder)
            System.out.printf("File: %s\n", f.getPath());
    }
    

    TreeTraverser также позволяет между различными стилями обхода.

    08.03.2016

    16

    Еще один оптимизированный код

    import java.io.File;
    import java.util.ArrayList;
    import java.util.List;
    
    public class GetFilesRecursive {
        public static List <String> getFilesRecursively(File dir){
            List <String> ls = new ArrayList<String>();
            if (dir.isDirectory())
                for (File fObj : dir.listFiles()) {
                    if(fObj.isDirectory()) {
                        ls.add(String.valueOf(fObj));
                        ls.addAll(getFilesRecursively(fObj));               
                    } else {
                        ls.add(String.valueOf(fObj));       
                    }
                }
            else
                ls.add(String.valueOf(dir));
    
            return ls;
        }
    
        public static void main(String[] args) {
            List <String> ls = getFilesRecursively(new File("/Users/srinivasab/Documents"));
            for (String file:ls) {
                System.out.println(file);
            }
            System.out.println(ls.size());
        }
    }
    
    17.04.2019
  • Пожалуйста, не могли бы вы расширить свой ответ более подробным объяснением? Это будет очень полезно для понимания. Спасибо! 17.04.2019

  • 17

    Еще один пример вывода списка файлов и каталогов с использованием Java 8 filter

    public static void main(String[] args) {
    
    System.out.println("Files!!");
            try {
                Files.walk(Paths.get("."))
                        .filter(Files::isRegularFile)
                        .filter(c ->
                                c.getFileName().toString().substring(c.getFileName().toString().length()-4).contains(".jpg")
                                ||
                                c.getFileName().toString().substring(c.getFileName().toString().length()-5).contains(".jpeg")
                        )
                        .forEach(System.out::println);
    
            } catch (IOException e) {
            System.out.println("No jpeg or jpg files");
            }
    
            System.out.println("\nDirectories!!\n");
            try {
                Files.walk(Paths.get("."))
                        .filter(Files::isDirectory)
                        .forEach(System.out::println);
    
            } catch (IOException e) {
                System.out.println("No Jpeg files");
            }
    }
    
    04.08.2020

    18
  • Пожалуйста, добавьте некоторые пояснения. 17.07.2015
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..