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


258

Як мені рекурсивно перераховувати всі файли в каталозі на Java? Чи надає рамка якусь корисність?

Я бачив багато реактивних реалізацій. Але ніхто з фреймворку чи ніо


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
Як порівняння продуктивності цього методу з методами pre-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 )

Редагувати: Ви можете перевірити тут тест на різні підходи. Здається , що Загально-IO підхід повільно, тому вибрати деякі з них швидше , звідси (якщо це важливо)


40
FYI / TLDR: якщо ви просто хочете перераховувати всі файли рекурсивно без фільтрування, зробіть FileUtils.listFiles(dir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE), де dirє об'єкт File, який вказує на базовий каталог.
andronikus

2
Ви можете розглянути можливість використання listFilesAndDirs(), оскільки listFiles()не повертає порожні папки.
schnatterer

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

простий і чистий
Лев

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

Удосконалений варіант публічної статичної колекції <File> listFileTree (Файл dir) {if (null == dir ||! Dir.isDirectory ()) {return Collections.emptyList (); } остаточний встановити <File> fileTree = новий HashSet <File> (); for (Запис файлу: dir.listFiles ()) {if (entry.isFile ()) {fileTree.add (entry); } else {fileTree.addAll (списокFileTree (запис)); }} повернути файлTree; }
Бен

Для мене це найкоротша відповідь, яка є рекурсивною.
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);
    }
  }
}

Але ваш алгоритм не може друкувати з відступом. Діри та файли змішані. Будь-яке рішення?
Вей

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

1
Будь ласка! нехай абонент ініціалізує список файлів, щоб не довелося кожен раз перевіряти його недійсність. Якщо ви хочете створити другий (загальнодоступний) метод, який створює список, викликає цей внутрішній метод і повертає повний список.
геліос

1
що завгодно. нульова перевірка не дуже дорога, зручність + особисті переваги вбік, я думаю, він отримає бал.
pstanton

Чи можете ви пояснити трохи докладніше?
удень

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

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

Нижче коду використовується підхід для відвідування на основі відвідувачів. Очікується, що вхід до програми - це кореневий каталог для проходження.

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

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, а потім виконувати свою дію у звичайній для кожного циклу. Таким чином, вам не доведеться обробляти винятки всередині лямбда.

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


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

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

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

1
    private void fillFilesRecursively(File file, List<File> resultFiles) {
        if (file.isFile()) {
            resultFiles.add(file);
        } else {
            for (File child : file.listFiles()) {
                fillFilesRecursively(child, resultFiles);
            }
        }
    }

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

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 на потоковий результат -

Сподіваюсь, це допомагає.


0

Котлін має FileTreeWalkдля цього. Наприклад:

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

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


0

Ще один спосіб ви можете зробити, навіть якщо хтось уже надає Java 8 прогулянку.

Цей файл надасть всі файли рекурсивно

  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>

1
Хоча це, мабуть, працює, питання стосується перегляду файлів, а не рендерингу переглянутих файлів. Краще розкрийте свій алгоритм як такий, не рекомендується вбудовувати бізнес-логіку в JSP.
Самуель Керрієн

Це залежить від того, що ти робиш. У програмі розміру підприємства ви абсолютно праві. Якщо ви просто потребуєте цього як випадок до простого, окремого списку, то це цілком чудово.
Nux
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.