Рекурсивный список файлов в Java

258

Как мне рекурсивно перечислить все файлы в каталоге в Java? Предоставляет ли фреймворк какую-либо полезность?

Я видел много хакерских реализаций. Но ни один из рамок или нио

Quintin Par
источник
2
Я только что завершил тестовые результаты, которые предоставляют тесты производительности для многих ответов. Неудивительно, что все ответы на основе NIO работают лучше всего. Ответ от commons-io явно худший, более чем вдвое больше.
Бретт Райан
2
Java8: Files.walk?
Бендж

Ответы:

327

Java 8 предоставляет хороший поток для обработки всех файлов в дереве.

Files.walk(Paths.get(path))
        .filter(Files::isRegularFile)
        .forEach(System.out::println);

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

ОБНОВЛЕНИЕ : Я мог бы указать, что есть также Files.find, который принимает BiPredicate, который может быть более эффективным, если вам нужно проверить атрибуты файла.

Files.find(Paths.get(path),
           Integer.MAX_VALUE,
           (filePath, fileAttr) -> fileAttr.isRegularFile())
        .forEach(System.out::println);

Обратите внимание, что, хотя JavaDoc не допускает, что этот метод может быть более эффективным, чем Files.walk, он фактически идентичен, различие в производительности можно наблюдать, если вы также извлекаете атрибуты файла в своем фильтре. В конце концов, если вам нужно фильтровать атрибуты, используйте Files.find , в противном случае используйте Files.walk. , в основном из-за перегрузок, и это более удобно.

ИСПЫТАНИЯ : В соответствии с просьбой я дал сравнение производительности многих ответов. Проверьте проект Github, который содержит результаты и контрольный пример .

Бретт Райан
источник
6
Один из тех примеров, которые могут показать магию функционального программирования даже для начинающих.
Джонни
2
Как производительность по сравнению с до-Java 8 методов? Мой текущий просмотр каталога слишком медленный, и я ищу что-то, что ускорит его.
Шридхар Сарнобат
1
Я пишу некоторые тесты, содержащие большинство вариантов в ответах. До сих пор кажется, что использование Files.walkс параллельным потоком является лучшим, за которым следует, Files.walkFileTreeчто только немного медленнее. Принятый ответ с использованием commons-io, безусловно, самый медленный по моим тестам, в 4 раза медленнее.
Бретт Райан
1
@BrettRyan, я попробовал ваше решение, но получил исключение Exception in thread "main" java.io.UncheckedIOException: java.nio.file.AccessDeniedException. Как я мог это исправить
Качна
5
Как мне получить фактический список файлов из этого?
Тулиха
159

У FileUtils есть iterateFilesи listFilesметоды. Дай им попробовать. (из общего достояния )

Изменить: вы можете проверить здесь для сравнения различных подходов. Кажется, что подход commons-io медленный, поэтому выберите некоторые из более быстрых отсюда (если это имеет значение)

Bozho
источник
40
FYI / TLDR: если вы хотите просто рекурсивно перечислить все файлы без фильтрации, выполните команду « FileUtils.listFiles(dir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE)Где», где dirнаходится объект «Файл», указывающий на базовый каталог.
andronikus
2
Вы можете рассмотреть возможность использования listFilesAndDirs(), так как listFiles()не возвращает пустые папки.
шнаттерер
1
@MikeFHay Глядя на код FileUtils, я думаю, что это будет FileUtils.listFiles(dir, true, true). Использование FileUtils.listFiles(dir, null, true)выдаст исключение, а FileUtils.listFiles(dir, true, null)перечислит все файлы, не заглядывая в подкаталоги.
Окрамот
Как насчет нативной библиотеки JDK? Я могу реализовать это легко, но я просто буду C & P из других мест
Кристиан Бонджорно
1
Я собираю некоторые тесты вместе, но пока, похоже, это работает в 4 раза медленнее, чем при использовании альтернатив JDK8 или JDK7. Символьные ссылки также оказываются проблематичными с этим подходом, особенно когда они ссылаются на каталоги выше в дереве, это приводит к тому, что метод никогда не возвращается, этого можно избежать, обрабатывая фильтр, но, к сожалению, сами символические ссылки не посещаются, даже если файл.
Бретт Райан
138

// Готов для запуска

import java.io.File;

public class Filewalker {

    public void walk( String path ) {

        File root = new File( path );
        File[] list = root.listFiles();

        if (list == null) return;

        for ( File f : list ) {
            if ( f.isDirectory() ) {
                walk( f.getAbsolutePath() );
                System.out.println( "Dir:" + f.getAbsoluteFile() );
            }
            else {
                System.out.println( "File:" + f.getAbsoluteFile() );
            }
        }
    }

    public static void main(String[] args) {
        Filewalker fw = new Filewalker();
        fw.walk("c:\\" );
    }

}
укладчик
источник
9
Просто помните, что для символических ссылок, которые указывают путь выше в иерархии путей, метод никогда не закончится. Рассмотрим путь с символической ссылкой, которая указывает на -> ..
Бретт Райан
2
Это просто плохая реализация Files.walkFileTree. Я бы порекомендовал, чтобы люди смотрели на FIles.walkFileTree вместо того, чтобы пытаться свернуть его сами ... У него есть обработка для точной проблемы, которую указал @BrettRyan.
Тайлер Николс
Спасибо, что включили импорт java.io.File ;. Многие примеры забывают включать в себя материал пространства имен или даже тип данных, что делает этот пример отправной точкой в ​​путешествии открытий. Вот этот пример готов к запуску. Спасибо.
barrypicker
Путь может варьироваться в зависимости от того, где находится файл Filewalker. Используйте "/", "./"или "../"для корневого каталога, текущий рабочий каталог и родительский каталог соответственно
Moses Kirathe
67

Java 7 будет иметь имеет Files.walkFileTree :

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

В настоящее время существует целый учебник Oracle по этому вопросу .

зевать
источник
И это никогда не уведомляет об окончании прогулки.
Фарид
25

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

public static Collection<File> listFileTree(File dir) {
    Set<File> fileTree = new HashSet<File>();
    if(dir==null||dir.listFiles()==null){
        return fileTree;
    }
    for (File entry : dir.listFiles()) {
        if (entry.isFile()) fileTree.add(entry);
        else fileTree.addAll(listFileTree(entry));
    }
    return fileTree;
}
Petrucio
источник
просто и чисто
Лев
17

Я хотел бы пойти с чем-то вроде:

public void list(File file) {
    System.out.println(file.getName());
    File[] children = file.listFiles();
    for (File child : children) {
        list(child);
    }
}

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

Штефан Шмидт
источник
6
Из документации listFiles(): «Если это абстрактное имя пути не обозначает каталог, то этот метод возвращает null».
HFS
Улучшенный вариант public static Collection <File> listFileTree (File dir) {if (null == dir ||! Dir.isDirectory ()) {return Collections.emptyList (); } final Set <File> fileTree = new HashSet <File> (); for (Запись файла: dir.listFiles ()) {if (entry.isFile ()) {fileTree.add (entry); } else {fileTree.addAll (listFileTree (entry)); }} return fileTree; }
Бен
Для меня это самый краткий ответ, который является рекурсивным.
WillieT
14

Я предпочитаю использовать очередь, а не рекурсию для такого простого прохождения:

List<File> allFiles = new ArrayList<File>();
Queue<File> dirs = new LinkedList<File>();
dirs.add(new File("/start/dir/"));
while (!dirs.isEmpty()) {
  for (File f : dirs.poll().listFiles()) {
    if (f.isDirectory()) {
      dirs.add(f);
    } else if (f.isFile()) {
      allFiles.add(f);
    }
  }
}
benroth
источник
Но ваш алгоритм не может печатать с отступом. Каталоги и файлы перепутаны. Любое решение?
Вэй
12

просто напишите это самостоятельно, используя простую рекурсию:

public List<File> addFiles(List<File> files, File dir)
{
    if (files == null)
        files = new LinkedList<File>();

    if (!dir.isDirectory())
    {
        files.add(dir);
        return files;
    }

    for (File file : dir.listFiles())
        addFiles(files, file);
    return files;
}
pstanton
источник
1
Пожалуйста! позвольте вызывающему инициализировать список файлов, чтобы ему не приходилось каждый раз проверять его недействительность. Если вы хотите создать второй (публичный) метод, который создает список, вызывает этот внутренний метод и возвращает полный список.
Гелиос
1
без разницы. нулевая проверка не очень дорога, кроме удобства и личных предпочтений, я думаю, он поймет.
pstanton
Можете ли вы объяснить более многословно?
Uday
8

Я думаю, что это должно сделать работу:

File dir = new File(dirname);
String[] files = dir.list();

Таким образом, у вас есть файлы и каталоги. Теперь используйте рекурсию и сделайте то же самое для dirs (у Fileкласса есть isDirectory()метод).

Михал Никлас
источник
8

С Java 7 вы можете использовать следующий класс:

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

public class MyFileIterator extends SimpleFileVisitor<Path>
{
    public MyFileIterator(String path) throws Exception
    {
        Files.walkFileTree(Paths.get(path), this);
    }

    @Override
    public FileVisitResult visitFile(Path file,
            BasicFileAttributes attributes) throws IOException
    {
        System.out.println("File: " + file);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult preVisitDirectory(Path dir,
            BasicFileAttributes attributes) throws IOException
    {
        System.out.println("Dir: " + dir);
        return FileVisitResult.CONTINUE;
    }
}
чао
источник
7

В Java 8 теперь мы можем использовать утилиту Files для обхода файлового дерева. Очень просто.

Files.walk(root.toPath())
      .filter(path -> !Files.isDirectory(path))
      .forEach(path -> System.out.println(path));
Рой Качух
источник
7

Этот код готов к запуску

public static void main(String... args) {
    File[] files = new File("D:/").listFiles();
    if (files != null) 
       getFiles(files);
}

public static void getFiles(File[] files) {
    for (File file : files) {
        if (file.isDirectory()) {
            getFiles(file.listFiles());
        } else {
            System.out.println("File: " + file);
        }
    }
}
Эбрахим Алраби
источник
4

Помимо рекурсивного обхода можно использовать подход на основе Visitor.

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

public interface Visitor {
    void visit(DirElement d);
    void visit(FileElement f);
}

public abstract class Element {
    protected File rootPath;
    abstract void accept(Visitor v);

    @Override
    public String toString() {
        return rootPath.getAbsolutePath();
    }
}

public class FileElement extends Element {
    FileElement(final String path) {
        rootPath = new File(path);
    }

    @Override
    void accept(final Visitor v) {
        v.visit(this);
    }
}

public class DirElement extends Element implements Iterable<Element> {
    private final List<Element> elemList;
    DirElement(final String path) {
        elemList = new ArrayList<Element>();
        rootPath = new File(path);
        for (File f : rootPath.listFiles()) {
            if (f.isDirectory()) {
                elemList.add(new DirElement(f.getAbsolutePath()));
            } else if (f.isFile()) {
                elemList.add(new FileElement(f.getAbsolutePath()));
            }
        }
    }

    @Override
    void accept(final Visitor v) {
        v.visit(this);
    }

    public Iterator<Element> iterator() {
        return elemList.iterator();
    }
}

public class ElementWalker {
    private final String rootDir;
    ElementWalker(final String dir) {
        rootDir = dir;
    }

    private void traverse() {
        Element d = new DirElement(rootDir);
        d.accept(new Walker());
    }

    public static void main(final String[] args) {
        ElementWalker t = new ElementWalker("C:\\temp");
        t.traverse();
    }

    private class Walker implements Visitor {
        public void visit(final DirElement d) {
            System.out.println(d);
            for(Element e:d) {
                e.accept(this);
            }
        }

        public void visit(final FileElement f) {
            System.out.println(f);
        }
    }
}
Sateesh
источник
3

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

public static void main(String args[]) {

        recusiveList("D:");

    }

    public static void recursiveList(String path) {

        File f = new File(path);
        File[] fl = f.listFiles();
        for (int i = 0; i < fl.length; i++) {
            if (fl[i].isDirectory() && !fl[i].isHidden()) {
                System.out.println(fl[i].getAbsolutePath());
                recusiveList(fl[i].getAbsolutePath());
            } else {
                System.out.println(fl[i].getName());
            }
        }
    }
Ракеш Чаудхари
источник
2

Общепринятый ответ велик, однако он ломается , когда вы хотите сделать IO внутри лямбда.

Вот что вы можете сделать, если ваше действие объявляет IOExceptions.

Вы можете рассматривать отфильтрованный поток как Iterable, а затем выполнять свои действия в обычном цикле for-each. Таким образом, вам не нужно обрабатывать исключения внутри лямбды.

try (Stream<Path> pathStream = Files.walk(Paths.get(path))
        .filter(Files::isRegularFile)) {

    for (Path file : (Iterable<Path>) pathStream::iterator) {
        // something that throws IOException
        Files.copy(file, System.out);
    }
}

Нашел этот трюк здесь: https://stackoverflow.com/a/32668807/1207791

cfstras
источник
1

Нерекурсивная BFS с одним списком (конкретный пример - поиск файлов * .eml):

    final FileFilter filter = new FileFilter() {
        @Override
        public boolean accept(File file) {
            return file.isDirectory() || file.getName().endsWith(".eml");
        }
    };

    // BFS recursive search
    List<File> queue = new LinkedList<File>();
    queue.addAll(Arrays.asList(dir.listFiles(filter)));

    for (ListIterator<File> itr = queue.listIterator(); itr.hasNext();) {
        File file = itr.next();
        if (file.isDirectory()) {
            itr.remove();
            for (File f: file.listFiles(filter)) itr.add(f);
        }
    }
BoBaH
источник
1

Моя версия (конечно, я мог бы использовать встроенную прогулку в Java 8 ;-)):

public static List<File> findFilesIn(File rootDir, Predicate<File> predicate) {
        ArrayList<File> collected = new ArrayList<>();
        walk(rootDir, predicate, collected);
        return collected;
    }

    private static void walk(File dir, Predicate<File> filterFunction, List<File> collected) {
        Stream.of(listOnlyWhenDirectory(dir))
                .forEach(file -> walk(file, filterFunction, addAndReturn(collected, file, filterFunction)));
    }

    private static File[] listOnlyWhenDirectory(File dir) {
        return dir.isDirectory() ? dir.listFiles() : new File[]{};
    }

    private static List<File> addAndReturn(List<File> files, File toAdd, Predicate<File> filterFunction) {
        if (filterFunction.test(toAdd)) {
            files.add(toAdd);
        }
        return files;
    }
user1189332
источник
1

Вот простое, но отлично работающее решение с использованием recursion:

public static List<Path> listFiles(String rootDirectory)
{
    List<Path> files = new ArrayList<>();
    listFiles(rootDirectory, files);

    return files;
}

private static void listFiles(String path, List<Path> collectedFiles)
{
    File root = new File(path);
    File[] files = root.listFiles();

    if (files == null)
    {
        return;
    }

    for (File file : files)
    {
        if (file.isDirectory())
        {
            listFiles(file.getAbsolutePath(), collectedFiles);
        } else
        {
            collectedFiles.add(file.toPath());
        }
    }
}
BullyWiiPlaza
источник
1
    private void fillFilesRecursively(File file, List<File> resultFiles) {
        if (file.isFile()) {
            resultFiles.add(file);
        } else {
            for (File child : file.listFiles()) {
                fillFilesRecursively(child, resultFiles);
            }
        }
    }
legendmohe
источник
1

Я придумал это для рекурсивной печати всех файлов / имен файлов.

private static void printAllFiles(String filePath,File folder) {
    if(filePath==null) {
        return;
    }
    File[] files = folder.listFiles();
    for(File element : files) {
        if(element.isDirectory()) {
            printAllFiles(filePath,element);
        } else {
            System.out.println(" FileName "+ element.getName());
        }
    }
}
kanaparthikiran
источник
0

Пример выводит файлы * .csv в подкаталоги рекурсивного поиска в каталоге с помощью Files.find () из java.nio:

String path = "C:/Daten/ibiss/ferret/";
    logger.debug("Path:" + path);
    try (Stream<Path> fileList = Files.find(Paths.get(path), Integer.MAX_VALUE,
            (filePath, fileAttr) -> fileAttr.isRegularFile() && filePath.toString().endsWith("csv"))) {
        List<String> someThingNew = fileList.sorted().map(String::valueOf).collect(Collectors.toList());
        for (String t : someThingNew) {
            t.toString();
            logger.debug("Filename:" + t);
        }

    }

После публикации этого примера у меня возникли проблемы с пониманием того, как передать параметр имени файла в примере №1, заданном Брайаном, с использованием foreach в Stream-result -

Надеюсь это поможет.

Ральф Р.
источник
0

Котлин FileTreeWalkдля этого есть. Например:

dataDir.walkTopDown().filter { !it.isDirectory }.joinToString("\n") {
   "${it.toRelativeString(dataDir)}: ${it.length()}"
}

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

Клайд
источник
0

Еще один способ сделать это можно, даже если кто-то уже предоставил Java 8 walk.

Этот предоставит вам все файлы рекурсивно

  private Stream<File> files(File file) {
    return file.isDirectory()
            ? Arrays.stream(file.listFiles()).flatMap(this::files)
            : Stream.of(file);
}
Майкл
источник
-1

На основании ответа укладчика. Вот решение, работающее в JSP без каких-либо внешних библиотек, поэтому вы можете разместить его практически в любом месте на вашем сервере:

<!DOCTYPE html>
<%@ page session="false" %>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<%@ page contentType="text/html; charset=UTF-8" %>

<%!
    public List<String> files = new ArrayList<String>();
    /**
        Fills files array with all sub-files.
    */
    public void walk( File root ) {
        File[] list = root.listFiles();

        if (list == null) return;

        for ( File f : list ) {
            if ( f.isDirectory() ) {
                walk( f );
            }
            else {
                files.add(f.getAbsolutePath());
            }
        }
    }
%>
<%
    files.clear();
    File jsp = new File(request.getRealPath(request.getServletPath()));
    File dir = jsp.getParentFile();
    walk(dir);
    String prefixPath = dir.getAbsolutePath() + "/";
%>

Тогда вы просто делаете что-то вроде:

    <ul>
        <% for (String file : files) { %>
            <% if (file.matches(".+\\.(apk|ipa|mobileprovision)")) { %>
                <li><%=file.replace(prefixPath, "")%></li>
            <% } %>
        <% } %>
    </ul>
Nux
источник
1
Хотя это, вероятно, работает, вопрос касается просмотра файлов, а не рендеринга просматриваемых файлов. Лучше представить свой алгоритм как таковой, не рекомендуется встраивать бизнес-логику в JSP.
Самуэль Керриен
Это зависит от того, что вы делаете. В приложении корпоративного размера вы абсолютно правы. Если вам просто нужно добавить это в простой, автономный список, то это прекрасно.
Nux