Melhor maneira de listar arquivos em Java, classificados por Data de modificação?

240

Quero obter uma lista de arquivos em um diretório, mas quero classificá-la para que os arquivos mais antigos sejam os primeiros. Minha solução foi chamar File.listFiles e apenas recorrer a lista com base em File.lastModified, mas eu queria saber se havia uma maneira melhor.

Edit: Minha solução atual, como sugerido, é usar um comparador anônimo:

File[] files = directory.listFiles();

Arrays.sort(files, new Comparator<File>(){
    public int compare(File f1, File f2)
    {
        return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
    } });
cwick
fonte
1
o que há com a parte "nova e longa" disso? por que você não compara os próprios desejos? que evitaria que criar toneladas de anseia apenas para chegar ao método compareTo ...
John Gardner
Este código não compila. Os métodos de comparação esperam que o retorno seja um int em vez de um Long.
Marcospereira 15/10/08
1
Eu sou o único que considera esta solução insana? Você está chamando file.lastModified()uma quantidade enorme de vezes. É melhor obter todas as datas primeiro e pedir depois, para que isso file.lastModified()seja chamado apenas uma vez por arquivo.
Cprcrack
1
Você pode usar o comparador apache commons:Arrays.sort(files, LastModifiedFileComparator.LASTMODIFIED_REVERSE);
jlunavtgrad
5
Existe uma solução melhor com o Java 8 (veja a resposta viniciussss):Arrays.sort(files, Comparator.comparingLong(File::lastModified));
starbroken 15/01/18

Respostas:

99

Eu acho que sua solução é a única maneira sensata. A única maneira de obter a lista de arquivos é usar File.listFiles () e a documentação afirma que isso não garante a ordem dos arquivos retornados. Portanto, você precisa escrever um Comparador que use File.lastModified () e passar isso, juntamente com a matriz de arquivos, para Arrays.sort () .

Dan Dyer
fonte
Como faço para corrigir a formatação aqui? Parece bom na pré-visualização, mas o quarto link está ferrado.
Dan Dyer
1
File.lastModified pode mudar ao classificar o resultado final em um Erro de violação do método de comparação, consulte: stackoverflow.com/questions/20431031 Consulte stackoverflow.com/a/4248059/314089 para obter uma possível solução melhor.
precisa saber é o seguinte
48

Isso pode ser mais rápido se você tiver muitos arquivos. Isso usa o padrão decorate-sort-undecorate para que a data da última modificação de cada arquivo seja buscada apenas uma vez, e não sempre que o algoritmo de classificação compara dois arquivos. Isso potencialmente reduz o número de chamadas de E / S de O (n log n) para O (n).

Porém, é mais código, portanto, isso só deve ser usado se você estiver preocupado principalmente com velocidade e for mensurável mais rápido na prática (o que eu não verifiquei).

class Pair implements Comparable {
    public long t;
    public File f;

    public Pair(File file) {
        f = file;
        t = file.lastModified();
    }

    public int compareTo(Object o) {
        long u = ((Pair) o).t;
        return t < u ? -1 : t == u ? 0 : 1;
    }
};

// Obtain the array of (file, timestamp) pairs.
File[] files = directory.listFiles();
Pair[] pairs = new Pair[files.length];
for (int i = 0; i < files.length; i++)
    pairs[i] = new Pair(files[i]);

// Sort them by timestamp.
Arrays.sort(pairs);

// Take the sorted pairs and extract only the file part, discarding the timestamp.
for (int i = 0; i < files.length; i++)
    files[i] = pairs[i].f;
Jason Orendorff
fonte
5
Melhor resposta, pois provavelmente é o único a impedir um "Erro de violação do método de comparação" se o lastModified for alterado durante a classificação?
precisa saber é o seguinte
1
Isso também deve ser usado quando você estiver preocupado em não obter IllegalArgumentException devido à violação do método de comparação. O método usando Map falhará se houver mais de um arquivo com o mesmo valor lastModified que resultaria na omissão desses arquivos. Definitivamente, essa deve ser uma resposta aceita.
Desenvolvedor Android
44

Solução elegante desde o Java 8:

File[] files = directory.listFiles();
Arrays.sort(files, Comparator.comparingLong(File::lastModified));

Ou, se você quiser em ordem decrescente, basta inverter:

File[] files = directory.listFiles();
Arrays.sort(files, Comparator.comparingLong(File::lastModified).reversed());
viniciussss
fonte
2
Esta é realmente a solução mais fácil. Para listas:files.sort(Comparator.comparingLong(File::lastModified));
starbroken 15/01
@starbroken Sua solução não funcionará se arquivos for uma matriz simples, como Arquivo [], retornada por directory.listFiles ().
22418 Viniciussss
@starbroken Para que sua solução funcione, é preciso usar ArrayList<File> files = new ArrayList<File>(Arrays.asList(directory.listFiles())), isso não é mais fácil do que apenas File[] files = directory.listFiles().
22418 Viniciussss
Sim, eu concordo com você. Se você tiver uma matriz de arquivos, não há motivo para criar uma lista. (Se alguém se perguntar, esse comentário "adicional" ArrayList<File>(...)em viniciussss é necessário para obter uma lista mutável que pode ser classificada.) Encontrei esse tópico procurando uma maneira de classificar uma lista de arquivos. Acabei de adicionar esse código para que as pessoas possam simplesmente copiá-lo se tiverem listas também.
starbroken
A Comparatorclasse não tem nenhuma chamada de métodocomparingLong
zeleven 28/01
37

O que é uma abordagem semelhante, mas sem boxe para os objetos Long:

File[] files = directory.listFiles();

Arrays.sort(files, new Comparator<File>() {
    public int compare(File f1, File f2) {
        return Long.compare(f1.lastModified(), f2.lastModified());
    }
});
PhannGor
fonte
Parece ser apenas a API 19+.
Gábor
4
Use return Long.valueOf (f1.lastModified ()). CompareTo (f2.lastModified ()); em vez de APIs mais baixas.
Martin Sykes
25

Você também pode observar o IO do apache commons , ele possui um comparador modificado pela última vez e muitos outros utilitários interessantes para trabalhar com arquivos.

user17163
fonte
5
Há um erro estranho no javadoc com esta solução, porque o javadoc diz para usar "LastModifiedFileComparator.LASTMODIFIED_COMPARATOR.sort (list);" para classificar uma lista, mas LASTMODIFIED_COMPARATOR é declarado como "Comparador <Arquivo>", portanto, não expõe nenhum método de "classificação".
Tristan
4
Use-o assim: link
cleroo 16/08
1
File.lastModified pode mudar ao classificar o resultado final em um Erro de violação do método de comparação, consulte: stackoverflow.com/questions/20431031 Consulte stackoverflow.com/a/4248059/314089 para obter uma possível solução melhor.
precisa saber é o seguinte
1
amo Apache Commons, que salvou um monte de tempo,
RedDevil
16

No Java 8:

Arrays.sort(files, (a, b) -> Long.compare(a.lastModified(), b.lastModified()));

hasen
fonte
13

Importações:

org.apache.commons.io.comparator.LastModifiedFileComparator

Apache Commons

Código:

public static void main(String[] args) throws IOException {
        File directory = new File(".");
        // get just files, not directories
        File[] files = directory.listFiles((FileFilter) FileFileFilter.FILE);

        System.out.println("Default order");
        displayFiles(files);

        Arrays.sort(files, LastModifiedFileComparator.LASTMODIFIED_COMPARATOR);
        System.out.println("\nLast Modified Ascending Order (LASTMODIFIED_COMPARATOR)");
        displayFiles(files);

        Arrays.sort(files, LastModifiedFileComparator.LASTMODIFIED_REVERSE);
        System.out.println("\nLast Modified Descending Order (LASTMODIFIED_REVERSE)");
        displayFiles(files);

    }
Balaji Boggaram Ramanarayan
fonte
Não está claro instantaneamente de onde o LastModifiedFileComparator.LASTMODIFIED_COMPARATOR foi obtido. Talvez adicionar link ao apache commons io ajudaria.
banda larga
Feito, Obrigado banda larga
Balaji Boggaram Ramanarayan 29/03
10

Se os arquivos que você está classificando podem ser modificados ou atualizados ao mesmo tempo, a classificação está sendo executada:


Java 8+

private static List<Path> listFilesOldestFirst(final String directoryPath) throws IOException {
    try (final Stream<Path> fileStream = Files.list(Paths.get(directoryPath))) {
        return fileStream
            .map(Path::toFile)
            .collect(Collectors.toMap(Function.identity(), File::lastModified))
            .entrySet()
            .stream()
            .sorted(Map.Entry.comparingByValue())
//            .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))  // replace the previous line with this line if you would prefer files listed newest first
            .map(Map.Entry::getKey)
            .map(File::toPath)  // remove this line if you would rather work with a List<File> instead of List<Path>
            .collect(Collectors.toList());
    }
}

Java 7

private static List<File> listFilesOldestFirst(final String directoryPath) throws IOException {
    final List<File> files = Arrays.asList(new File(directoryPath).listFiles());
    final Map<File, Long> constantLastModifiedTimes = new HashMap<File,Long>();
    for (final File f : files) {
        constantLastModifiedTimes.put(f, f.lastModified());
    }
    Collections.sort(files, new Comparator<File>() {
        @Override
        public int compare(final File f1, final File f2) {
            return constantLastModifiedTimes.get(f1).compareTo(constantLastModifiedTimes.get(f2));
        }
    });
    return files;
}


Ambas as soluções criam uma estrutura de dados de mapa temporária para economizar um tempo constante da última modificação para cada arquivo no diretório. O motivo pelo qual precisamos fazer isso é que, se seus arquivos estiverem sendo atualizados ou modificados enquanto sua classificação está sendo executada, seu comparador estará violando o requisito de transitividade do contrato geral da interface do comparador, pois os últimos tempos de modificação podem estar mudando durante a comparação.

Se, por outro lado, você sabe que os arquivos não serão atualizados ou modificados durante a sua classificação, poderá obter praticamente qualquer outra resposta enviada a esta pergunta, da qual sou parcial:

Java 8+ (nenhuma modificação simultânea durante a classificação)

private static List<Path> listFilesOldestFirst(final String directoryPath) throws IOException {
    try (final Stream<Path> fileStream = Files.list(Paths.get(directoryPath))) {
        return fileStream
            .map(Path::toFile)
            .sorted(Comparator.comparing(File::lastModified))
            .map(File::toPath)  // remove this line if you would rather work with a List<File> instead of List<Path>
            .collect(Collectors.toList());
    }
}

Nota: Eu sei que você pode evitar a conversão de e para objetos File no exemplo acima, usando a API Files :: getLastModifiedTime na operação de fluxo classificado; no entanto, você precisa lidar com exceções de IO verificadas dentro do lambda, o que é sempre um problema. . Eu diria que se o desempenho é crítico o suficiente para que a tradução seja inaceitável, eu lidaria com a IOException verificada no lambda propagando-a como uma UncheckedIOException ou renunciaria à API do Files por completo e lidaria apenas com os objetos File:

final List<File> sorted = Arrays.asList(new File(directoryPathString).listFiles());
sorted.sort(Comparator.comparing(File::lastModified));
Matthew Madson
fonte
2
public String[] getDirectoryList(String path) {
    String[] dirListing = null;
    File dir = new File(path);
    dirListing = dir.list();

    Arrays.sort(dirListing, 0, dirListing.length);
    return dirListing;
}
Calvin Schultz
fonte
1
Na verdade, isso não classifica a propriedade de data de modificação mencionada na pergunta. A função de classificação usará a ordem natural do objeto File, que é o lexicográfico dependente do sistema no nome do caminho .
Matt Chan
2
Collections.sort(listFiles, new Comparator<File>() {
        public int compare(File f1, File f2) {
            return Long.compare(f1.lastModified(), f2.lastModified());
        }
    });

Onde listFilesestá a coleção de todos os arquivos em ArrayList

Anand Savjani
fonte
1

Você pode tentar a goiaba Ordenação :

Function<File, Long> getLastModified = new Function<File, Long>() {
    public Long apply(File file) {
        return file.lastModified();
    }
};

List<File> orderedFiles = Ordering.natural().onResultOf(getLastModified).
                          sortedCopy(files);
Vitalii Fedorenko
fonte
1

Você pode usar a biblioteca Apache LastModifiedFileComparator

 import org.apache.commons.io.comparator.LastModifiedFileComparator;  


File[] files = directory.listFiles();
        Arrays.sort(files, LastModifiedFileComparator.LASTMODIFIED_COMPARATOR);
        for (File file : files) {
            Date lastMod = new Date(file.lastModified());
            System.out.println("File: " + file.getName() + ", Date: " + lastMod + "");
        }
Vikas
fonte
1
private static List<File> sortByLastModified(String dirPath) {
    List<File> files = listFilesRec(dirPath);
    Collections.sort(files, new Comparator<File>() {
        public int compare(File o1, File o2) {
            return Long.compare(o1.lastModified(), o2.lastModified());
        }
    });
    return files;
}
Jaydev
fonte
0

Eu vim para este post quando estava procurando pelo mesmo problema, mas em android . Não digo que essa é a melhor maneira de obter arquivos classificados pela data da última modificação, mas é a maneira mais fácil que encontrei ainda.

O código abaixo pode ser útil para alguém-

File downloadDir = new File("mypath");    
File[] list = downloadDir.listFiles();
    for (int i = list.length-1; i >=0 ; i--) {
        //use list.getName to get the name of the file
    }

obrigado

Hirdesh Vishwdewa
fonte
Mas quem faz a classificação?
DAB
na parte de inicialização do forloop, você pode ver que eu list.length-1adotei o i >=0que simplesmente itera na ordem inversa.
Hirdesh Vishwdewa
0

Existe uma maneira muito fácil e conveniente de lidar com o problema sem nenhum comparador extra. Apenas codifique a data modificada na String com o nome do arquivo, classifique-a e depois retire-a novamente.

Use uma String de comprimento fixo 20, coloque a data modificada (longa) nela e preencha com zeros à esquerda. Em seguida, basta anexar o nome do arquivo a esta sequência:

String modified_20_digits = ("00000000000000000000".concat(Long.toString(temp.lastModified()))).substring(Long.toString(temp.lastModified()).length()); 

result_filenames.add(modified_20_digits+temp.getAbsoluteFile().toString());

O que acontece é isso aqui:

Nome do arquivo1: C: \ data \ file1.html Última modificação: 1532914451455 Última modificação 20 dígitos: 00000001532914451455

Nome do arquivo1: C: \ data \ file2.html Última modificação: 1532918086822 Última modificação 20 dígitos: 00000001532918086822

transforma nomes de arquivos em:

Nome do arquivo1: 00000001532914451455C: \ data \ file1.html

Nome do arquivo2: 00000001532918086822C: \ data \ file2.html

Você pode apenas classificar esta lista.

Tudo o que você precisa fazer é remover os 20 caracteres novamente mais tarde (no Java 8, você pode removê-lo para toda a matriz com apenas uma linha usando a função .replaceAll)

user4378029
fonte
-1

Existe também uma maneira completamente diferente, que pode ser ainda mais fácil, pois não lidamos com grandes números.

Em vez de classificar toda a matriz depois de recuperar todos os nomes de arquivos e datas lastModified, você pode simplesmente inserir todos os nomes de arquivos logo após recuperá-los na posição correta da lista.

Você pode fazer assim:

list.add(1, object1)
list.add(2, object3)
list.add(2, object2)

Depois de adicionar o objeto2 à posição 2, ele moverá o objeto3 para a posição 3.

user4378029
fonte