Лучший способ перечислить файлы в Java, отсортированные по дате изменения?

240

Я хочу получить список файлов в каталоге, но хочу отсортировать его так, чтобы самые старые файлы были первыми. Мое решение состояло в том, чтобы вызвать File.listFiles и просто прибегнуть к списку, основанному на File.lastModified, но мне было интересно, есть ли лучший способ.

Изменить: мое текущее решение, как предлагается, заключается в использовании анонимного компаратора:

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
источник
1
что с "новой длинной" частью этого? почему бы тебе не сравнить самих длинных? это позволило бы вам не создавать тонны длинных позиций только для того, чтобы перейти к методу сравнения ...
Джон Гарднер
Этот код не компилируется. методы сравнения ожидают, что возвращаемое значение будет int вместо Long.
Marcospereira
1
Я единственный, кто считает это решение безумным? Вы звоните file.lastModified()огромное количество раз. Лучше сначала получить все даты, а потом заказывать, чтобы они file.lastModified()вызывались только один раз для каждого файла.
cprcrack
1
Вы можете использовать Apache Commons Arrays.sort(files, LastModifiedFileComparator.LASTMODIFIED_REVERSE);
Compara
5
Есть лучшее решение с Java 8 (см. Ответ viniciussss):Arrays.sort(files, Comparator.comparingLong(File::lastModified));
разбито звездой

Ответы:

99

Я думаю, что ваше решение - единственный разумный путь. Единственный способ получить список файлов - это использовать File.listFiles (), и в документации говорится, что это не дает никаких гарантий относительно порядка возвращаемых файлов. Поэтому вам нужно написать Comparator, который использует File.lastModified () и передать его вместе с массивом файлов в Arrays.sort () .

Дэн Дайер
источник
Как мне исправить форматирование здесь? Хорошо выглядит в превью, но 4-я ссылка прикручена.
Дэн Дайер
1
File.lastModified может измениться при сортировке конечного результата из-за ошибки нарушения метода сравнения, см. Stackoverflow.com/questions/20431031 См. Stackoverflow.com/a/4248059/314089 для возможного лучшего решения.
icyerasor
48

Это может быть быстрее, если у вас много файлов. При этом используется шаблон decorate-sort-undecorate, чтобы дата последнего изменения каждого файла выбиралась только один раз, а не каждый раз, когда алгоритм сортировки сравнивает два файла. Это потенциально уменьшает количество вызовов ввода / вывода с O (n log n) до O (n).

Тем не менее, это больше кода, поэтому его следует использовать только в том случае, если вы в основном заинтересованы в скорости, и на практике это заметно быстрее (что я не проверял).

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;
Джейсон Орендорфф
источник
5
Лучший ответ, так как он, вероятно, единственный, который предотвращает «ошибку нарушения метода сравнения», если lastModified изменяется во время сортировки?
icyerasor
1
Это также следует использовать, если вы не хотите получить исключение IllegalArgumentException из-за нарушения метода сравнения. Метод с использованием Map завершится ошибкой, если существует более одного файла с одинаковым значением lastModified, что приведет к пропуску этих файлов. Это определенно должен быть принятый ответ.
Android-разработчик
44

Элегантное решение начиная с Java 8:

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

Или, если вы хотите в порядке убывания, просто измените его:

File[] files = directory.listFiles();
Arrays.sort(files, Comparator.comparingLong(File::lastModified).reversed());
viniciussss
источник
2
Это действительно самое простое решение. Для списков:files.sort(Comparator.comparingLong(File::lastModified));
разбито звездой
@starbroken Ваше решение не работает, если файлы представляют собой простой массив, такой как File [], который возвращается directory.listFiles ().
viniciussss
@starbroken Чтобы ваше решение работало, нужно использовать его ArrayList<File> files = new ArrayList<File>(Arrays.asList(directory.listFiles())), что не проще, чем просто File[] files = directory.listFiles().
viniciussss
Да, я согласен с тобой. Если у вас есть массив файлов, нет причин создавать список. (Если кому-то интересно, это «дополнительное» ArrayList<File>(...)в комментарии viniciussss необходимо для получения изменяемого списка, который можно отсортировать.) Я нашел эту ветку в поисках способа сортировки списка файлов. Поэтому я просто добавил этот код, чтобы люди могли просто скопировать его, если у них тоже есть списки.
разбито
ComparatorКласс не имеет какой - либо вызов методаcomparingLong
zeleven
37

Что касается аналогичного подхода, но без привязки к объектам 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
источник
Кажется, это только API 19+.
Габор
4
Используйте return Long.valueOf (f1.lastModified ()). CompareTo (f2.lastModified ()); вместо этого для более низких API.
Мартин Сайкс
25

Вы также можете посмотреть на Apache commons IO , в него встроен последний измененный компаратор и множество других полезных утилит для работы с файлами.

user17163
источник
5
В этом решении есть странная ошибка в javadoc, потому что javadoc говорит использовать «LastModifiedFileComparator.LASTMODIFIED_COMPARATOR.sort (list);» для сортировки списка, но LASTMODIFIED_COMPARATOR объявлен как «Comparator <File>», поэтому он не предоставляет никакого метода «sort».
Тристан
4
Используйте это так: ссылка
cleroo
1
File.lastModified может измениться при сортировке конечного результата из-за ошибки нарушения метода сравнения, см. Stackoverflow.com/questions/20431031 См. Stackoverflow.com/a/4248059/314089 для возможного лучшего решения.
icyerasor
1
люблю Apache Commons, что сэкономило много времени,
RedDevil
16

В Java 8:

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

Hasen
источник
13

Импорт:

org.apache.commons.io.comparator.LastModifiedFileComparator

Apache Commons

Код:

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

    }
Баладжи Боггарам Раманараян
источник
Не сразу понятно, откуда берется LastModifiedFileComparator.LASTMODIFIED_COMPARATOR. Может быть, добавление ссылки на Apache Commons IO поможет.
широкополосный
Готово, спасибо широкополосной связи
Balaji Boggaram Ramanarayan
10

Если сортируемые файлы могут быть изменены или обновлены одновременно, сортировка выполняется:


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


Оба эти решения создают временную структуру данных карты для сохранения постоянного времени последнего изменения для каждого файла в каталоге. Причина, по которой мы должны это сделать, заключается в том, что если ваши файлы обновляются или модифицируются во время выполнения сортировки, тогда ваш компаратор будет нарушать требование транзитивности общего контракта интерфейса компаратора, поскольку время последнего изменения может изменяться во время сравнения.

Если, с другой стороны, вы знаете, что файлы не будут обновляться или изменяться во время сортировки, вы можете получить практически любой другой ответ, представленный на этот вопрос, к которому я неравнодушен:

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

Примечание: я знаю, что вы можете избежать преобразования в и из объектов File в приведенном выше примере, используя api Files :: getLastModifiedTime в операции отсортированного потока, однако тогда вам придется иметь дело с проверенными исключениями ввода-вывода внутри лямбды, что всегда является проблемой , Я бы сказал, что если производительность достаточно критична, чтобы перевод был неприемлемым, то я бы либо имел дело с проверенным IOException в лямбда-выражении, передав его как UncheckedIOException, либо я бы вообще отказался от файлов api и имел дело только с объектами File:

final List<File> sorted = Arrays.asList(new File(directoryPathString).listFiles());
sorted.sort(Comparator.comparing(File::lastModified));
Мэтью Мэдсон
источник
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;
}
Кельвин Шульц
источник
1
На самом деле это не сортирует по дате изменения свойства, которое было упомянуто в вопросе. Функция сортировки будет использовать естественный порядок объекта File, который является системно-зависимым лексикографическим по имени пути .
Мэтт Чан
2
Collections.sort(listFiles, new Comparator<File>() {
        public int compare(File f1, File f2) {
            return Long.compare(f1.lastModified(), f2.lastModified());
        }
    });

где listFilesнаходится коллекция всех файлов в ArrayList

Ананд Савьяни
источник
1

Вы можете попробовать гуавы Заказ :

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);
Виталий Федоренко
источник
1

Вы можете использовать библиотеку 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 + "");
        }
Викас
источник
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
источник
0

Я пришел к этому сообщению, когда искал ту же проблему, но в android. Я не говорю, что это лучший способ получить отсортированные файлы по дате последнего изменения, но это самый простой способ, который я нашел.

Ниже код может быть полезен для кого-то

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
    }

Спасибо

Хирдеш Вишдева
источник
Но кто занимается сортировкой?
DAB
в части инициализации forцикла вы можете видеть, что я list.length-1поднялся, i >=0что просто повторяет вас в обратном порядке.
Hirdesh Vishwdewa
0

Существует очень простой и удобный способ решения проблемы без дополнительного компаратора. Просто закодируйте измененную дату в строку с именем файла, отсортируйте ее, а затем снова удалите.

Используйте строку фиксированной длины 20, поместите в нее измененную дату (длинную) и заполните начальными нулями. Затем просто добавьте имя файла к этой строке:

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

Что здесь происходит, это:

Имя файла1: C: \ data \ file1.html Последнее изменение: 1532914451455 Последнее изменение 20 цифр: 00000001532914451455

Имя файла1: C: \ data \ file2.html Последнее изменение: 1532918086822 Последнее изменение 20 цифр: 00000001532918086822

преобразует имена файлов в:

Имя файла1: 00000001532914451455C: \ data \ file1.html

Имя файла2: 00000001532918086822C: \ data \ file2.html

Затем вы можете просто отсортировать этот список.

Все, что вам нужно сделать, это снова обрезать 20 символов позже (в Java 8 вы можете обрезать его для всего массива всего одной строкой, используя функцию .replaceAll)

user4378029
источник
-1

Существует также совершенно другой способ, который может быть даже проще, поскольку мы не имеем дело с большими числами.

Вместо того, чтобы сортировать весь массив после того, как вы извлекли все имена файлов и даты lastModified, вы можете просто вставить каждое отдельное имя файла сразу после того, как вы извлекли его в правильной позиции списка.

Вы можете сделать это так:

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

После того, как вы добавите объект 2 в положение 2, он переместит объект 3 в положение 3.

user4378029
источник