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

85

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

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

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());
    }
  }
}

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

Hultner
источник
1
... какой вопрос? Вы просто ищете подтверждение того, что этот код будет работать?
Ричард JP Le Guen
Нет, я знаю, что этот код работает, но он очень медленный, и кажется, что это глупый доступ к файловой системе и получение содержимого для каждого подкаталога вместо получения всего сразу.
Hultner
1
возможный дубликат файлов рекурсивного списка в Java
Прахалад Гаггар

Ответы:

134

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

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

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

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

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

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

В Java 8, это 1-вкладыш через 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
Богемный
источник
3
Я предлагаю всегда использовать те методы Files, которые возвращают Stream в блоках try-with-resources: в противном случае вы сохраните ресурс открытым
riccardo.tasso
Разве терминальные операции не вызывают close в самом потоке?
Драгас
@ Драгас, да. Мой потребитель - всего лишь простой пример; в реальной жизни вы бы сделали что-нибудь более полезное.
Богемский
27

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

Он использует класс 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;
} 
Дэн
источник
18

С 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;
        }
    });
}
jboi
источник
5
Хороший ответ :), есть также реализованный класс под названием "SimpleFileVisitor", если вам не нужны все реализованные функции, вы можете просто переопределить необходимые функции.
GalDude33
13

Быстрый способ получить содержимое каталога с помощью 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();
RealHowTo
источник
3
Хорошо, но получает файлы только для одного каталога. Если вы хотите увидеть все подкаталоги, см. Мой альтернативный ответ.
Дэн
3
Files.newDirectoryStreamможет вызвать исключение IOException. Я предлагаю обернуть эту строку в Java7 try-with-statement, чтобы поток всегда был закрыт для вас (исключение или нет, без необходимости в a finally). См. Также здесь: stackoverflow.com/questions/17739362/…
Грег
12

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

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

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

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

Кевин Дэй
источник
3
Java 7 теперь существует, поэтому был бы полезен пример того, как это сделать на Java 7. Или хотя бы ссылку. Или название класса для поиска в Google. - это все-таки «stackoverflow», а не «теоретическая cs» ;-).
Мартин
3
Что ж, давайте посмотрим ... Моя первоначальная публикация была в марте 2010 года ... Сейчас январь 2012 года ... Я только что проверил историю инвентаризации своего оборудования и не вижу, что у меня была машина времени в марте 2010 года, так что я думаю, что я, вероятно, вправе отвечать, не приводя явного примера ;-)
Кевин Дэй
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
        }
    }
}
Пратамеш савант
источник
1
Хороший ответ, если вам нужно что-то, что работает с java <7.
ssimm
3

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

String[] types = {"mp3", "flac"};
Collection<File> files2 = FileUtils.listFiles(/path/to/your/dir, types , true);
thouliha
источник
3

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

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);
        }
    }
}

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

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

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]);
        }

    }

}
Вишал Мокал
источник
1
В этом примере не учитывается тот факт, что метод listFiles () может возвращать значение null. docs.oracle.com/javase/7/docs/api/java/io/File.html#listFiles ()
Мэтт Джонс,
1

Java 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);
        }


    }
Нирадж Сонаване
источник
0

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

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

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

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

Дэниел Райан
источник
0

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

Kiran
источник
0
import java.io.*;

public class MultiFolderReading {

public void checkNoOfFiles (String filename) throws IOException {

    File dir=new File(filename);
    File files[]=dir.listFiles();//files array stores the list of files

 for(int i=0;i<files.length;i++)
    {
        if(files[i].isFile()) //check whether files[i] is file or directory
        {
            System.out.println("File::"+files[i].getName());
            System.out.println();

        }
        else if(files[i].isDirectory())
        {
            System.out.println("Directory::"+files[i].getName());
            System.out.println();
            checkNoOfFiles(files[i].getAbsolutePath());
        }
    }
}

public static void main(String[] args) throws IOException {

    MultiFolderReading mf=new MultiFolderReading();
    String str="E:\\file"; 
    mf.checkNoOfFiles(str);
   }
}
праджакта
источник
Пожалуйста, добавьте также некоторые пояснения.
d4Rk
0

В 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 также позволяет переключаться между различными стилями обхода.

Марк Юний Брут
источник
0
public class GetFilesRecursive {
    public static List <String> getFilesRecursively(File dir){
        List <String> ls = new ArrayList<String>();
        for (File fObj : dir.listFiles()) {
            if(fObj.isDirectory()) {
                ls.add(String.valueOf(fObj));
                ls.addAll(getFilesRecursively(fObj));               
            } else {
                ls.add(String.valueOf(fObj));       
            }
        }

        return ls;
    }
    public static List <String> getListOfFiles(String fullPathDir) {
        List <String> ls = new ArrayList<String> ();
        File f = new File(fullPathDir);
        if (f.exists()) {
            if(f.isDirectory()) {
                ls.add(String.valueOf(f));
                ls.addAll(getFilesRecursively(f));
            }
        } else {
            ls.add(fullPathDir);
        }
        return ls;
    }

    public static void main(String[] args) {
        List <String> ls = getListOfFiles("/Users/srinivasab/Documents");
        for (String file:ls) {
            System.out.println(file);
        }
        System.out.println(ls.size());
    }
}
Шри
источник
0

Другой оптимизированный код

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());
    }
}
Шри
источник
Не могли бы вы дополнить свой ответ более подробными пояснениями? Это будет очень полезно для понимания. Спасибо!
vezunchik
0

Еще один пример перечисления файлов и каталогов с использованием 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");
        }
}
Уддхав Гаутам
источник