Найкращий спосіб переліку файлів на 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());
    } });

1
що з "новою довгою" частиною цього? чому б ти просто не порівняв самі довгі? це дозволить уникнути створення тонни довгих, щоб дістатися до методу порівнянняДо…
Джон Гарднер,

Цей код не компілюється. Методи порівняння очікують, що повернення є int замість Лонга.
marcospereira

1
Я єдиний, хто вважає це рішення божевільним? Вам дзвонить file.lastModified()величезна кількість разів. Краще отримати всі дати спочатку та замовити пізніше, так що file.lastModified()це називається лише один раз у файлі.
cprcrack

1
Ви можете скористатися Arrays.sort(files, LastModifiedFileComparator.LASTMODIFIED_REVERSE);
апаратом

5
Є краще рішення з Java 8 (див. Відповідь viniciussss):Arrays.sort(files, Comparator.comparingLong(File::lastModified));
зірваний

Відповіді:


99

Я думаю, що ваше рішення - єдиний розумний спосіб. Єдиний спосіб отримати список файлів - це використовувати File.listFiles (), і в документації зазначено, що це не дає гарантій щодо порядку повернення файлів. Тому вам потрібно написати компаратор, який використовує File.lastModified (), і передати це разом з масивом файлів до Arrays.sort () .


Як тут виправити форматування? Виглядає добре в попередньому перегляді, але четверте посилання накручується.
Дан Дайер

1
File.lastModified може змінитися під час сортування кінцевого результату у помилку порушення методу порівняння, див. Stackoverflow.com/questions/20431031 Дивіться stackoverflow.com/a/4248059/314089 для можливого кращого рішення.
icyerasor

48

Це може бути швидше, якщо у вас багато файлів. При цьому використовується шаблон оформлення-сортування-підкреслення, так що остання змінена дата кожного файлу отримується лише один раз, а не кожен раз, коли алгоритм сортування порівнює два файли. Це потенційно зменшує кількість викликів вводу / виводу з 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
Найкраща відповідь, адже це, мабуть, єдине, що запобігає помилці порушення методу порівняння, якщо остання змінена змінюється під час сортування?
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());

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

Що стосується подібного підходу, але без боксу до Довгих об'єктів:

File[] files = directory.listFiles();

Arrays.sort(files, new Comparator<File>() {
    public int compare(File f1, File f2) {
        return Long.compare(f1.lastModified(), f2.lastModified());
    }
});

Здається, це лише API 19+.
Габор

4
Використовуйте return Long.valueOf (f1.lastModified ()). ПорівнятиTo (f2.lastModified ()); замість нижчих апі.
Мартін Сайкс

25

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


5
Існує дивна помилка javadoc з цим рішенням, тому що javadoc каже використовувати "LastModifiedFileComparator.LASTMODIFIED_COMPARATOR.sort (список);" сортувати список, але LASTMODIFIED_COMPARATOR оголошується як "Порівняльник <Файл>", тому він не піддається жодному методу "сортування".
Трістан

4
Використовуйте його так: посилання
cleroo

1
File.lastModified може змінитися під час сортування кінцевого результату у помилку порушення методу порівняння, див. Stackoverflow.com/questions/20431031 Дивіться stackoverflow.com/a/4248059/314089 для можливого кращого рішення.
icyerasor

1
love apache commons, що економило багато часу,
redDevil


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 допоможе.
широкосмуговий

Готово, Спасибі широкосмуговий
Баладжі Боггарам Раманараян

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

Примітка. Я знаю, що ви можете уникнути перекладу до та з Файлових об’єктів у наведеному вище прикладі, використовуючи файл Files :: getLastModifiedTime api в режимі відсортованого потоку, однак тоді вам потрібно мати справу з перевіреними винятками IO всередині вашої лямбда, що завжди є болем . Я б сказав, якщо продуктивність достатньо критична, що переклад неприйнятний, то я б або мав справу з перевіреною IOException в лямбда, розповсюджуючи її як UncontIOException, або я б відмовився від файлів 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;
}

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

Існує дуже простий і зручний спосіб вирішити проблему без зайвого порівняння. Просто введіть модифіковану дату в String з назвою файлу, відсортуйте її та пізніше зніміть.

Використовуйте рядок фіксованої довжини 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)


-1

Існує також зовсім інший спосіб, який може бути ще простішим, оскільки ми не маємо справу з великою кількістю.

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

Ви можете це зробити так:

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

Після того, як ви додасте object2 у позицію 2, він перемістить object3 у позицію 3.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.