Java ile bir dizindeki tüm dosyaları yinelemeli olarak listeleyin


86

Bir dizindeki tüm dosyaların adını yinelemeli olarak yazdıran bu işleve sahibim. Sorun şu ki kodum çok yavaş çünkü her yinelemede uzaktaki bir ağ cihazına erişmesi gerekiyor.

Planım, önce dizindeki tüm dosyaları yinelemeli olarak yüklemek ve ardından istemediğim tüm dosyaları filtrelemek için normal ifadeyle tüm dosyaları gözden geçirmek. Daha iyi bir önerisi olan var mı?

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

Bu daha sonra sadece bir test olacak, kodu böyle kullanmayacağım, bunun yerine bir diziye gelişmiş bir normal ifade ile eşleşen her dosyanın yolunu ve değiştirme tarihini ekleyeceğim.


1
... soru nedir? Sadece bu kodun çalışacağına dair bir doğrulama mı arıyorsunuz?
Richard JP Le Guen

Hayır, bu kodun çalıştığını biliyorum ama çok yavaş ve her şeyi bir kerede almak yerine dosya sistemine erişim ve her alt dizinin içeriğini almak aptalca geliyor.
Hultner

Yanıtlar:


134

- Bu varsayarsak o zaman ben zaten çözülmüş oldu bir şey bu tür çözüm kullanmanızı öneririz, Yazmakta alınacak gerçek üretim kodu Apache Commons IO özellikle FileUtils.listFiles(). İç içe geçmiş dizinleri, filtreleri (isme, değişiklik zamanına vb. Göre) işler.

Örneğin, normal ifadeniz için:

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

Bu, ^(.*?)normal ifadeyle eşleşen dosyaları yinelemeli olarak arayacak ve sonuçları bir koleksiyon olarak döndürecektir.

Bunun kendi kodunuzu yuvarlamaktan daha hızlı olmayacağını belirtmekte fayda var, aynı şeyi yapıyor - Java'da bir dosya sisteminde gezinmek çok yavaş. Aradaki fark, Apache Commons sürümünde hiç hata olmamasıdır.


Oraya baktım ve bundan, dizinden ve alt dizinlerden tüm dosyayı almak için commons.apache.org/io/api-release/index.html?org/apache/commons/… ' i kullanırdım ve ardından dosyalar arasında arama yapardım , böylece normal ifademle eşleşiyorlar. Yoksa yanılıyor muyum?
Hultner

Evet sorun, klasörü taramak bir saatten fazla sürüyor ve güncellemeleri kontrol etmek için programı her başlattığımda bunu yapmak son derece can sıkıcı. Programın bu kısmını C'de ve geri kalanını Java'da yazsaydım daha hızlı olur muydu ve eğer öyleyse önemli bir fark olur muydu? Şimdilik, if isdir satırındaki kodu değiştirdim ve dizinin aramaya dahil edilmesi için bir normal ifadeyle eşleşmesi gerekecek şekilde ekledim. Örneğinizde DirectoryFileFilter.DIRECTORY yazdığını görüyorum, sanırım orada bir normal ifade filtresine sahip olabilirim.
Hultner

1
yerel çağrılar kullanarak yazmak kesinlikle daha hızlı hale getirir - FindFirstFile / FineNextFile, dosya özniteliklerini ayrı bir çağrı yapmanıza gerek kalmadan sorgulamanıza izin verir - bu, daha yüksek gecikmeli ağlar için çok büyük etkilere sahip olabilir. Java'nın bu konudaki yaklaşımı korkunç derecede verimsiz.
Kevin Günü

5
@ hanzallah-afgan: Hem soru hem de cevap 5 yaşın üzerinde. Geçmişte iki büyük Java sürümü olmuştur, bu nedenle Java 7 NIO gibi daha yeni özellikleri araştırmak istemeyebilirsiniz.
Hultner

4
FileUtils'i yalnızca performans vuruşunu biliyorsanız ve kabul ediyorsanız kullanın: github.com/brettryan/io-recurse-tests . Yerel java8 alternatifleri kısa ve daha verimli bir gösterim sağlar, örneğin:Files.walk(Paths.get("/etc")).filter(Files::isRegularFile).collect(Collectors.toList())
ccpizza

66

Java 8, bu aracılığıyla 1-liner Files.find()bir keyfi büyük derinliğiyle (örneğin 999) ve BasicFileAttributesbirisRegularFile()

public static printFnames(String sDir) {
    Files.find(Paths.get(sDir), 999, (p, bfa) -> bfa.isRegularFile()).forEach(System.out::println);
}

Daha fazla filtreleme eklemek için lambdayı geliştirin, örneğin son 24 saatte değiştirilen tüm jpg dosyaları:

(p, bfa) -> bfa.isRegularFile()
  && p.getFileName().toString().matches(".*\\.jpg")
  && bfa.lastModifiedTime().toMillis() > System.currentMillis() - 86400000

3
Kaynaklarla deneme bloklarında Akış döndüren bu Dosyalar yöntemlerini her zaman kullanmanızı öneririm: aksi takdirde kaynağı açık tutarsınız
riccardo.tasso

Terminal operasyonları akışın kendisine yakın çağrıda bulunmaz mı?
Dragas

@Dragas evet. Tüketicim sadece basit bir örnek; gerçek hayatta daha yararlı bir şey yapardın.
Bohemian

27

Bu, tüm dosyaları belirli bir kökten almak için çok basit bir özyinelemeli yöntemdir.

Java 7 NIO Path sınıfını kullanır.

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 ile bir dizin ağacında gezinmenin daha hızlı bir yolu Pathsve Filesişlevselliği ile tanıtıldı . "Eski" Fileyoldan çok daha hızlılar.

Bu, yol adlarını düzenli bir ifadeyle kontrol etmek için kullanılan koddur:

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
Güzel cevap :), ayrıca "SimpleFileVisitor" adında gerçeklenmiş bir sınıf var, eğer uygulanan tüm işlevlere ihtiyacınız yoksa, sadece gerekli işlevleri geçersiz kılabilirsiniz.
GalDude33

13

Java 7 NIO kullanarak bir dizinin içeriğini almanın hızlı yolu:

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
Güzel, ancak yalnızca bir dizindeki dosyaları alır. Tüm alt dizinleri görmek istiyorsanız alternatif cevabımı görün.
Dan

3
Files.newDirectoryStreambir IOException oluşturabilir. Bu satırı bir Java7 deneme ifadesiyle sarmalamanızı öneririm, böylece akış sizin için her zaman kapatılır (a'ya gerek kalmadan istisna olsun veya olmasın finally). Ayrıca buraya bakın: stackoverflow.com/questions/17739362/…
Greg

12

Java'nın dosya sistemi klasör içeriğini okumak için arayüzü çok başarılı değil (keşfettiğiniz gibi). JDK 7, bu tür şeyler için tamamen yeni bir arayüzle bunu düzeltir ve bu tür işlemlere yerel düzeyde performans getirmelidir.

Temel sorun, Java'nın her dosya için yerel bir sistem çağrısı yapmasıdır. Düşük gecikmeli bir arayüzde, bu o kadar da önemli değil - ancak orta derecede gecikmeli bir ağda, gerçekten artıyor. Yukarıdaki algoritmanızın profilini çıkarırsanız, zamanın büyük bir kısmının sinir bozucu isDirectory () çağrısında harcandığını göreceksiniz - bunun nedeni, her bir isDirectory () çağrısı için bir gidiş-dönüş yolculuk yapmanızdır. Çoğu modern işletim sistemi, başlangıçta dosya / klasör listesi istendiğinde bu tür bilgileri sağlayabilir (özellikleri için her bir dosya yolunu sorgulamanın aksine).

JDK7'yi bekleyemiyorsanız, bu gecikmeyi ele almak için bir strateji, çok iş parçacıklı gitmek ve özyinelemenizi gerçekleştirmek için maksimum iş parçacığı sayısına sahip bir ExecutorService kullanmaktır. Harika değil (çıktı veri yapılarınızın kilitlenmesi ile uğraşmanız gerekiyor), ancak bunu tek iş parçacıklı yapmaktan çok daha hızlı olacak.

Bu tür şeylerle ilgili tüm tartışmalarınızda, yerel kodu (veya kabaca aynı şeyi yapan bir komut satırı komut dosyasını) kullanarak yapabileceğiniz en iyi şeyle karşılaştırmanızı şiddetle tavsiye ederim. Bir ağ yapısını geçmenin bir saat sürdüğünü söylemek o kadar da bir şey ifade etmiyor. 7 saniyede native olarak yapabileceğinizi, ancak Java'da bir saat sürdüğünü söylemek insanların dikkatini çekecektir.


3
Java 7 artık orada, bu nedenle Java 7'de nasıl yapılacağına dair bir örnek yardımcı olacaktır. Veya en azından bir bağlantı. Veya google'da aranacak bir sınıf adı. - bu "yığın taşması" dır ve sonuçta "teorik cs" değildir ;-).
Martin

3
iyi bakalım ... Orijinal gönderim Mart 2010'daydı ... Şimdi Ocak 2012 ... Ve ekipman envanter geçmişimi kontrol ettim ve Mart 10'da bir zaman makinem olduğunu görmüyorum, bu yüzden açık bir örnek vermeden cevaplamamın muhtemelen haklı olduğunu düşünüyorum ;-)
Kevin Day


7

bu gayet iyi çalışacak ... ve özyinelemeli

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 ile çalışan bir şey istiyorsanız iyi yanıt.
ssimm

3

Ben şahsen FileUtils'in bu sürümünü beğendim. Bir dizindeki veya alt dizinlerindeki tüm mp3'leri veya flac'leri bulan bir örnek:

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

3

Bu iyi çalışacak

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'e hoş geldiniz, cevabınızın nasıl bir iyileştirme veya mevcut birçok cevaba alternatif olduğunu açıklayabilir misiniz?
Lilienthal

1

Bu İşlev muhtemelen tüm dosya adını ve dizininden ve alt dizinlerinden yolunu listeleyecektir.

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
Bu örnek, listFiles () yönteminin null döndürebileceği ve döneceği gerçeğini hesaba katmaz. docs.oracle.com/javase/7/docs/api/java/io/File.html#listFiles ()
Matt Jones

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

her şeyi bir kerede almak yerine dosya sistemine erişim ve her alt dizinin içeriğini almak aptalca geliyor.

Hislerin yanlış. Dosya sistemleri böyle çalışır. Daha hızlı bir yol yoktur (bunu tekrar tekrar yapmanız veya farklı desenler için yapmanız gerekmediği sürece, bellekteki tüm dosya yollarını önbelleğe alabilirsiniz, ancak daha sonra önbellek geçersiz kılma ile uğraşmanız gerekir, yani dosyalar eklendiğinde / kaldırıldığında / yeniden adlandırıldığında ne olur? uygulama çalışır).


Mesele şu ki, kullanıcıya sunulan bir kitaplığa belirli bir ad biçimine sahip belirli bir türdeki tüm dosyaları yüklemek istiyorum ve uygulama her başlatıldığında kitaplığın güncellenmesi gerekiyor, ancak kitaplığı güncellemek sonsuza kadar sürüyor. Elimdeki tek çözüm, güncellemeyi arka planda çalıştırmaktır, ancak tüm yeni dosyaların yüklenmesinin çok uzun sürmesi yine de can sıkıcı. Bunu yapmanın daha iyi bir yolu olmalı. Ya da en azından veritabanını güncellemenin daha iyi bir yolu. Zaten içinden geçtiği tüm dosyaları gözden geçirmesi aptalca geliyor. Güncellemeleri yalnızca hızlı bulmanın bir yolu var mı?
Hultner

@Hultner: Java 7, dosya sistemi güncellemelerinden haberdar olmak için bir araç içerecek, ancak bu yine de yalnızca uygulama çalışırken çalışacaktır, bu nedenle her zaman bir arka plan hizmetinin çalışmasını istemediğiniz sürece yardımcı olmaz. Kevin'in açıkladığı gibi ağ paylaşımlarında özel sorunlar olabilir, ancak tüm dizin ağacını taramaya bağlı olduğunuz sürece, gerçekten daha iyi bir yol yoktur.
Michael Borgwardt

Belki bazı dizin dosyaları oluşturabilirsiniz. Dizin boyutunu kontrol etmenin bir yolu varsa, boyut değiştiğinde yeni dosyaları tarayabilirsiniz.
James P.

@James: Dizin boyutunu kontrol etmenin bir yolu yok. Bir dizinin boyutu, bildiğim tüm dosya sistemlerinde her dosyanın boyutu alınarak ve bunların toplanmasıyla elde edilir. Aslında "bu dizinin boyutu nedir?" sabit bağlantıları düşündüğünüzde, gereksiz bir şekilde mantıklı bile değil.
Michael Borgwardt

Haklısın. Hala bazı önbelleğe alma ve / veya parmak izi alma işleminin süreci hızlandırabileceğini hissediyorum.
James P.

0

IsDirectory () yönteminin oldukça yavaş bir yöntem olduğunu bilmenizi isterim. Dosya tarayıcımda oldukça yavaş buluyorum. Yerel kodla değiştirmek için bir kitaplığa bakacağım.


0

Milyonlarca klasör ve dosya ile uğraşırken bulduğum daha verimli yol, bir dosyada DOS komutuyla dizin listesini yakalamak ve ayrıştırmaktır. Verileri ayrıştırdıktan sonra, analiz yapabilir ve istatistikleri hesaplayabilirsiniz.


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

Lütfen bir açıklama da ekleyin.
d4Rk

0

Guava'da bir Koleksiyonun size iade edilmesini beklemeniz gerekmez, ancak aslında dosyalar üzerinde yineleme yapabilirsiniz. IDoSomethingWithThisFileAşağıdaki işlevin imzasında bir arayüz hayal etmek kolaydır :

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 ayrıca çeşitli geçiş stilleri arasında geçiş yapmanızı sağlar.


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

Optimize edilmiş başka bir kod

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

Lütfen cevabınızı daha detaylı bir açıklama ile uzatır mısınız? Bu, anlamak için çok faydalı olacaktır. Teşekkür ederim!
vezunchik

0

Java 8 kullanarak dosya ve dizinleri listelemeye bir örnek daha 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");
        }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.