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

Це лише тест пізніше. Я не буду використовувати такий код, натомість додаю шлях і дату модифікації кожного файлу, який відповідає розширеному регулярному виразу, до масиву.


1
... в чому питання? Ви просто шукаєте підтвердження того, що цей код буде працювати?
Richard JP Le Guen,

Ні, я знаю, що цей код працює, але він дуже повільний, і здається, що це дурний доступ до файлової системи та отримання вмісту для кожного підкаталогу, замість того, щоб отримувати все одразу.
Hultner

Відповіді:


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

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, які повертають Потік у блоках try-with-resources: інакше ви збережете ресурс відкритим
riccardo.tasso

Хіба термінальні операційні виклики не закриваються на самому потоці?
Драгаш

@Dragas так. Мій споживач - лише простий приклад; в реальному житті ви зробили б щось більш корисне.
Чеська

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

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

3
Приємно, але отримує файли лише для одного каталогу. Якщо ви хочете побачити всі підкаталоги, перегляньте мою альтернативну відповідь.
Dan

3
Files.newDirectoryStreamможе викинути IOException. Я пропоную загорнути цей рядок у випробувальний оператор Java7, щоб потік завжди був закритим для вас (виняток чи ні, без потреби в 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 року ... І я щойно перевірив історію інвентаризації обладнання, і я не бачу, щоб мав машину часу ще в березні 10 року, тому я думаю, що я, мабуть, виправдано відповісти, не даючи явного прикладу ;-)
Кевін Дей


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-файли або файли у каталозі або будь-якому з його підкаталогів:

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

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 () може і поверне нуль. 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 P.

@James: немає можливості перевірити розмір каталогу. Розмір каталогу отримується шляхом отримання розміру кожного файлу та додавання їх у всіх відомих мені файлових системах. Власне, питання "який розмір цього каталогу?" навіть зовсім не має сенсу взагалі, якщо ви розглядаєте жорсткі посилання.
Michael Borgwardt

Ти маєш рацію. Я все ще відчуваю, що деяке кешування та / або відбитки пальців можуть пришвидшити процес.
James P.

0

Щоб ви знали, що isDirectory () - досить повільний метод. Я вважаю, що це досить повільно у моєму браузері файлів. Я загляну в бібліотеку, щоб замінити її власним кодом.


0

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


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

У Гуаві вам не потрібно чекати повернення колекції, але ви можете переглядати файли. Легко уявити 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");
        }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.