Список всех файлов из каталога, рекурсивно с 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());
    }
  }
}

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

16 71

16 ответов:

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

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

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

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

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

в 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

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

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

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

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

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

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

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

быстрый способ получить содержимое каталога с помощью 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();

это будет работать просто отлично ... и его рекурсивный

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

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

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

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

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

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

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

    }

}

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

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

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

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

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

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

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


    }