Liste todos os arquivos de um diretório recursivamente com Java


86

Eu tenho essa função que imprime o nome de todos os arquivos em um diretório recursivamente. O problema é que meu código é muito lento porque precisa acessar um dispositivo de rede remoto a cada iteração.

Meu plano é primeiro carregar todos os arquivos do diretório recursivamente e depois passar por todos os arquivos com o regex para filtrar todos os arquivos que não quero. Alguém tem uma sugestão melhor?

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

Este é apenas um teste posterior. Não vou usar o código como este, em vez disso, vou adicionar o caminho e a data de modificação de cada arquivo que corresponde a um regex avançado a um array.


1
... Qual é a questão? Você está apenas procurando a validação de que esse código funcionará?
Richard JP Le Guen

Não, eu sei que este código funciona, mas é muito lento e parece que é estúpido acessar o sistema de arquivos e obter o conteúdo de cada subdiretório em vez de obter tudo de uma vez.
Hultner

Respostas:


134

Presumindo que este seja o código de produção real que você estará escrevendo, sugiro usar a solução para esse tipo de coisa que já foi resolvida - Apache Commons IO , especificamente FileUtils.listFiles(). Ele lida com diretórios aninhados, filtros (com base no nome, hora de modificação, etc).

Por exemplo, para seu regex:

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

Isso irá pesquisar recursivamente por arquivos que correspondam ao ^(.*?)regex, retornando os resultados como uma coleção.

É importante notar que isso não será mais rápido do que lançar seu próprio código, está fazendo a mesma coisa - vasculhar um sistema de arquivos em Java é lento. A diferença é que a versão Apache Commons não terá bugs.


Olhei lá e a partir daí usaria commons.apache.org/io/api-release/index.html?org/apache/commons/… para obter todos os arquivos do diretório e subdiretórios e, em seguida, pesquisar os arquivos para que eles correspondem ao meu regex. Ou eu estou errado?
Hultner

Sim, problema, leva mais de uma hora para verificar a pasta e fazer isso toda vez que eu iniciar o programa para verificar se há atualizações é extremamente irritante. Seria mais rápido se eu escrevesse esta parte do programa em C e o resto em Java e se sim, haveria alguma diferença significativa? Por enquanto, mudei o código na linha if isdir e adicionei para que o diretório também tenha que corresponder a um regex para ser incluído na pesquisa. Vejo que em seu exemplo diz DirectoryFileFilter.DIRECTORY, acho que poderia ter um filtro regex lá.
Hultner

1
gravá-lo usando chamadas nativas o tornaria absolutamente mais rápido - FindFirstFile / FineNextFile permite que você consulte os atributos do arquivo sem ter que fazer uma chamada separada para ele - isso pode ter implicações massivas para redes de latência mais alta. A abordagem do Java para isso é terrivelmente ineficiente.
Kevin Day

5
@ hanzallah-afgan: Tanto a pergunta quanto a resposta têm mais de 5 anos. Já ocorreram duas versões principais do Java no passado, então você pode querer investigar os recursos mais recentes, como o Java 7 NIO.
Hultner

4
Use o FileUtils apenas se estiver ciente e aceitar o acerto de desempenho: github.com/brettryan/io-recurse-tests . Alternativas nativas de java8 permitem uma notação concisa e mais eficiente, por exemplo:Files.walk(Paths.get("/etc")).filter(Files::isRegularFile).collect(Collectors.toList())
ccpizza

66

No Java 8, é uma via de 1 linha Files.find()com uma profundidade arbitrariamente grande (por exemplo 999) e BasicFileAttributesdeisRegularFile()

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

Para adicionar mais filtragem, aprimore o lambda, por exemplo, todos os arquivos jpg modificados nas últimas 24 horas:

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

3
Eu sugiro sempre usar os métodos Files que retornam Stream em blocos try-with-resources: caso contrário, você manterá o recurso aberto
riccardo.tasso

As operações de terminal não chamam o fechamento no próprio fluxo?
Dragas

@Dragas sim. Meu consumidor é apenas um exemplo simples; na vida real, você faria algo mais útil.
Boêmio

27

Este é um método recursivo muito simples para obter todos os arquivos de uma determinada raiz.

Ele usa a classe 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

Com o Java 7, uma maneira mais rápida de percorrer uma árvore de diretórios foi introduzida com a funcionalidade Pathse Files. Eles são muito mais rápidos do que o Filemétodo "antigo" .

Este seria o código para percorrer e verificar os nomes dos caminhos com uma expressão regular:

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
Boa resposta :), há também uma classe implementada dele chamada "SimpleFileVisitor", se você não precisa de todas as funções implementadas, você pode simplesmente substituir as funções necessárias.
GalDude33

13

A maneira mais rápida de obter o conteúdo de um diretório usando 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
Bom, mas só obtém arquivos para um diretório. Se você quiser ver todos os subdiretórios, consulte minha resposta alternativa.
Dan de

3
Files.newDirectoryStreampode lançar uma IOException. Eu sugiro envolver essa linha em uma instrução try-with-Java7 para que o fluxo sempre seja fechado para você (exceção ou não, sem a necessidade de a finally). Veja também aqui: stackoverflow.com/questions/17739362/…
Greg

12

A interface do Java para ler o conteúdo da pasta do sistema de arquivos não tem muito desempenho (como você descobriu). O JDK 7 corrige isso com uma interface completamente nova para esse tipo de coisa, que deve trazer desempenho de nível nativo para esses tipos de operações.

O problema principal é que o Java faz uma chamada de sistema nativo para cada arquivo. Em uma interface de baixa latência, isso não é um grande problema - mas em uma rede com latência moderada, isso realmente faz sentido. Se você criar o perfil de seu algoritmo acima, verá que a maior parte do tempo é gasta na chata isDirectory () - isso porque você está incorrendo em uma viagem de ida e volta para cada chamada para isDirectory (). A maioria dos sistemas operacionais modernos pode fornecer esse tipo de informação quando a lista de arquivos / pastas foi solicitada originalmente (ao contrário de consultar cada caminho de arquivo individual para suas propriedades).

Se você não puder esperar pelo JDK7, uma estratégia para lidar com essa latência é usar vários threads e usar um ExecutorService com no máximo # de threads para realizar sua recursão. Não é ótimo (você tem que lidar com o bloqueio de suas estruturas de dados de saída), mas será muito mais rápido do que fazer esse único thread.

Em todas as suas discussões sobre esse tipo de coisa, eu recomendo fortemente que você compare com o melhor que você poderia fazer usando código nativo (ou mesmo um script de linha de comando que faz quase a mesma coisa). Dizer que leva uma hora para percorrer uma estrutura de rede não significa muito. Dizendo que você pode fazer isso nativo em 7 segundos, mas leva uma hora em Java vai chamar a atenção das pessoas.


3
O Java 7 já está aí, portanto, um exemplo de como fazê-lo no Java 7 seria útil. Ou pelo menos um link. Ou um nome de classe para pesquisar no google. - isto é «stackoverflow» e não «cs teórico» afinal ;-).
Martin

3
Bem, vamos ver ... Minha postagem original foi em março de 2010 ... Agora é janeiro de 2012 ... E eu acabei de verificar o meu histórico de inventário de equipamentos, e não me vejo tendo uma máquina do tempo em março de '10, então eu acho que provavelmente estou justificado em responder sem dar um exemplo explícito ;-)
Kevin Day


7

isso vai funcionar bem ... e é recursivo

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
Boa resposta se você quiser algo que funcione com java <7.
ssimm

3

Eu pessoalmente gosto dessa versão do FileUtils. Aqui está um exemplo que encontra todos os mp3s ou flacs em um diretório ou qualquer um de seus subdiretórios:

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

3

Isso vai funcionar bem

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


Bem-vindo ao StackOverflow Mam's, você poderia esclarecer como sua resposta é uma melhoria ou alternativa às muitas respostas existentes?
Lilienthal

1

Esta função provavelmente irá listar todo o nome do arquivo e seu caminho de seu diretório e seus subdiretórios.

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
Este exemplo não leva em consideração o fato de que o método listFiles () pode e irá retornar null. 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

parece que é estúpido acessar o sistema de arquivos e obter o conteúdo de cada subdiretório em vez de obter tudo de uma vez.

Seu sentimento está errado. É assim que os sistemas de arquivos funcionam. Não há maneira mais rápida (exceto quando você tem que fazer isso repetidamente ou para padrões diferentes, você pode armazenar em cache todos os caminhos de arquivo na memória, mas então você tem que lidar com a invalidação do cache, ou seja, o que acontece quando os arquivos são adicionados / removidos / renomeados enquanto o aplicativo é executado).


O fato é que eu quero carregar todos os arquivos de um determinado tipo com um determinado formato de nome em uma biblioteca que é apresentada ao usuário e toda vez que o aplicativo é iniciado, a biblioteca deve ser atualizada, mas leva uma eternidade para atualizar a biblioteca. A única solução que consegui foi executar a atualização em segundo plano, mas ainda é irritante que demore tanto tempo até que todos os novos arquivos sejam carregados. Deve haver uma maneira melhor de fazer isso. Ou pelo menos uma maneira melhor de atualizar o banco de dados. Parece estúpido examinar todos os arquivos que já examinou algumas vezes. Existe uma maneira de apenas encontrar atualizações rapidamente.
Hultner

@Hultner: Java 7 incluirá um recurso para ser notificado sobre atualizações do sistema de arquivos, mas isso ainda funcionaria apenas enquanto o aplicativo estiver em execução, então, a menos que você queira ter um serviço em segundo plano rodando o tempo todo, não ajudaria. Pode haver problemas especiais com compartilhamentos de rede, como Kevin descreve, mas, contanto que você dependa da varredura de toda a árvore de diretórios, realmente não há maneira melhor.
Michael Borgwardt

Talvez você possa criar alguns arquivos de índice. Se houver uma maneira de verificar o tamanho do diretório, você pode simplesmente procurar novos arquivos quando o tamanho mudar.
James P.

@James: não há como verificar o tamanho do diretório. O tamanho de um diretório é obtido obtendo o tamanho de cada arquivo e adicionando-os, em todos os sistemas de arquivos que conheço. Na verdade, a pergunta "qual é o tamanho deste diretório?" nem mesmo necessariamente faz sentido se você considerar hardlinks.
Michael Borgwardt

Você está certo. Ainda acho que algum armazenamento em cache e / ou impressão digital pode acelerar o processo.
James P.

0

Só para você saber, isDirectory () é um método muito lento. Estou achando muito lento no meu navegador de arquivos. Estarei procurando em uma biblioteca para substituí-la por código nativo.


0

A maneira mais eficiente que encontrei para lidar com milhões de pastas e arquivos é capturar a lista de diretórios por meio do comando DOS em algum arquivo e analisá-la. Depois de analisar os dados, você pode fazer análises e computar estatísticas.


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

Adicione alguma explicação também.
d4Rk

0

No Guava, você não precisa esperar que uma coleção seja devolvida a você, mas pode realmente iterar os arquivos. É fácil imaginar uma IDoSomethingWithThisFileinterface na assinatura da função abaixo:

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 também permite que você entre vários estilos de passagem.


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

Outro código otimizado

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

Por favor, você pode estender sua resposta com uma explicação mais detalhada? Isso será muito útil para a compreensão. Obrigado!
vezunchik

0

Mais um exemplo de listagem de arquivos e diretórios usando 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");
        }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.