Программирование на Java. Сортировка списка файлов.

Владимир | | Java.

В прошлой статье «Поиск файлов» мы начали разработку небольшой библиотеки для поиска файлов. На данный момент мы можем найти файлы, но не можем упорядочить результаты поиска. В этой статье я покажу, как создать класс, который позволит сортировать список файлов любым, удобным нам, способом.

Вступление
Прежде всего, нам нужно определиться, что мы будем сортировать, и как. Наш класс для поиска файлов имеет несколько методов find(...), которые возвращают список файлов (объект типа List) с объектами типа File. Таким образом, мы можем получить любую информацию о найденных файлах (имя, размер, размещение и т.д.).

Теперь решим, каким образом нам нужно отсортировать результаты поиска. Наиболее часто требуется сортировка по имени, типу, размеру и дате создания файла.

В этой статье мы напишем методы, необходимые для сортировки по имени файла (это одна из наиболее сложных сортировок). Итак, нам нужно, отсортировать все найденные файлы и папки в следующем порядке:

  • первыми идут файлы с минимальной глубиной вложения (т.е. те, которые находятся ближе к начальной папке поиска);
  • файлы, которые находятся внутри одной папки, должны быть отсортированы в алфавитном порядке (нужно обеспечить правильную обработку не латинских символов).

Результаты сортировки должны быть возвращены в виде списка (List) с объектами типа File, т.е. мы меняем только порядок следования элементов и ничего более.

Сортировка списка файлов на Java

Теперь, когда задача ясна, посмотрим, как мы можем её решить. В первую очередь, нам нужен какой-нибудь алгоритм сортировки. С этим проблем нет. На сегодняшний день, разработано множество таких алгоритмов. Парочку этих алгоритмов можно найти практически в любом учебнике по программированию. Так что, если хотите, берите книжку…, или можно воспользоваться стандартной библиотекой Java, конкретнее, методом sort(List list, Comparator c) класса Collections из пакета java.util.

Этот метод выполняет сортировку списка объектов, который передается ему в первом параметре (list). Тут у вас может возникнуть вполне закономерный вопрос: «А как именно он будет сортировать файлы». Ответ простой: «Так, как мы ему расскажем»:-). Дело в том, что любой алгоритм сортировки принимает решения о порядке следования объектов на основании результатов их сравнения, т.е. при сортировке мы всегда должны иметь возможность получить результат сравнения двух любых объектов. Например, если бы мы сортировали список с объектами стандартного типа, например, int, то метод sort упорядочил бы их в порядке возрастания без дополнительных усилий с нашей стороны.

Но о том, как мы хотим отсортировать наш список, методу sort ничего не известно. Поэтому мы должны написать метод, который выполняет сравнение двух объектов из нашего списка. Этот метод (int compare(Object o1, Object o2)) определён в интерфейсе Comparator. Он должен возвращать «1», если первый аргумент (o1) больше второго (o2), «0» — если аргументы равны, и «-1» — если второй объект больше. Как вы, наверное, уже поняли, указатель на класс, который содержит наш метод сравнения (compare(...)) передаётся методу sort во втором параметре. Метод sort будет сортировать наши объекты в порядке возрастания, сравнивая их при помощи нашего метода compare(...). Изменяя метод compare(...) мы можем задать любой порядок сортировки.

Теперь посмотрим как будет выглядеть наш класс сортировки файлов (FileSorter)

package searchtools;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.File;

/**
 * Этот класс предназначен для сортировки списка файлов
 *
 * @author Стаценко Владимир
 * http://www.vova-prog.narod.ru
 */
public class FileSorter implements Comparator {

    Pattern p = null;
    Collator collator = null;

    /** Создает новые экземпляры FileSorter */
    public FileSorter() {
        .
        .
        .
    }

    /* Этот метод выполняет сравнение имен двух файлов.
     * Возвращает:
     *     1 если первый параметр (о1) больше второго (о2),
     *    -1 если первый параметр (о1) меньше второго (о2),
     *     0 если они равны.
     * Имя первого файла считается больше второго имени, если
     * первый файл находится ближе к корню дерева папок.
     * Если файлы находятся в одной папке, то больше то имя,
     * которое идет первым по алфавиту.
     */
    public int compare(Object o1, Object o2) {
        .
        .
        .
    }

    public List sort(List fileList) {
        ArrayList res = new ArrayList(fileList.size());
        res.addAll(fileList);
        Collections.sort(res, this);
        return res;
    }
}

Как видите, наш класс реализует интерфейс Comparator, и, соответственно, методу sort мы передаём указатель this.

Для того, чтобы наш класс заработал, нам осталось написать метод compare.

Тут все просто. В первую очередь, проверяем равенство объектов (если имена файлов одинаковы, то и файлы равны). Если файлы разные, определяем их глубину вложения. Как вы помните, сначала должны идти файлы с меньшей глубиной вложения. Наконец, если файлы находятся на одной глубине, сравниваем сами имена файлов.

Для определения глубины вложения файлов нам нужно узнать количество символов-разделителей в полном имени файла. Обратите внимание, определять символ-разделитель нужно с помощью переменной File.separator, т.к. он зависит от операционной системы.

Для подсчёта символов-разделителей можно использовать цикл, но есть и более удобный метод, основанный на использовании регулярных выражений (почитать о них вы можете в статье «Анализ данных с помощью регулярных выражений или быстрый способ проверки введенных данных» или в Java Tutorial).

Для поддержки различных кодировок очень удобно использовать классы Collator и Locale из пакета java.util. Метод compare класса Collator позволяет выполнить сортировку строк в соответствии с алфавитом языка, который установлен в настройках системы.

Теперь посмотрим на весь класс целиком.

/*
 * FileSorter.java
 *
*/

package searchtools;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.File;

/**
 * Этот класс предназначен для сортировки списка файлов
 *
 * @author Стаценко Владимир
 * http://www.vova-prog.narod.ru
 */
public class FileSorter implements Comparator {

    //класс для работы с регулярными выражениями
    Pattern p = null;
    //класс для работы со строками на разных языках
    Collator collator = null;

    /** Создает новые экземпляры FileSorter */
    public FileSorter() {
        //определяем системный символ разделитель и создаем
        //шаблон на его основе
        String separator = File.separator;
        if(separator.equals("\\")) {
            separator = "\\";
        }
        //создаем шаблон на основе символа-разделителя
        p = Pattern.compile(separator,
                Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE);
        //получаем системные настройки (язык и страну)
        String country = System.getProperty("user.country");
        String language = System.getProperty("user.language");
        //создаем экземпляр класса для сравнения строк на
        //основе региональных настроек
        collator = Collator.getInstance(new Locale(country, language));
    }

    /**
     * Этот метод выполняет сравнение имен двух файлов.
     * Возвращает:
     *     1 если первый параметр (о1) больше второго (о2),
     *    -1 если первый параметр (о1) меньше второго (о2),
     *     0 если они равны.
     * Имя первого файла считается больше второго имени, если
     * первый файл находится ближе к корню дерева папок.
     * Если файлы находятся в одной папке, то больше то имя,
     * которое идет первым по алфавиту.
     * @param o1 объект типа File
     * @param o2 объект типа File
     * @return результат сравнения
     */
    public int compare(Object o1, Object o2) {
        //если объекты не равны null и имеют тип File
        if(o1 != null && o2 != null &&
                o1 instanceof File && o2 instanceof File) {
            //приводим к типу File
            File f1 = (File)o1;
            File f2 = (File)o2;
            //получаем полный путь к имени файла
            String fullPath1 = f1.getAbsolutePath();
            String fullPath2 = f2.getAbsolutePath();
            //проверяем равенство имен
            if(fullPath1.equals(fullPath2)) {
                //возвращаем 0, т.к. имена одинаковы
                return 0;
            }
            //определяем глубину размещения файла в дереве папок
            //для этого разбиваем полный путь к файлу на
            //лексемы, и определяем их количество
            String[] res1 = p.split(fullPath1);
            String[] res2 = p.split(fullPath2);
            if(res1.length > res2.length) {
                //возвращаем 1, если глубина вложения первого
                //файла больше глубины вложения второго
                return 1;
            }
            if(res1.length < res2.length) {
                //возвращаем "-1" в противном случае
                return -1;
            }
            if(res1.length == res2.length) {
                //если файлы находятся на одинаковой глубине,
                //сортируем их в соответствии с алфавитом
                return collator.compare(fullPath1, fullPath2);
            }
        }
        //здесь мы возвращаем 0, т.к. сравнение объектов
        //выполнить невозможно (т.е. считаем, что объекты
        //одинаковые, во всяком случае, сортировать их
        //нет смысла)
        return 0;
    }

    /**
     * Этот метод выполняет сортировку списка файлов
     * @param fileList не отсортированный список файлов
     * @return отсортированный список файлов
     */
    public List sort(List fileList) {
        //создаем список для результатов (такого же размера
        //как и исходный список)
        ArrayList res = new ArrayList(fileList.size());
        //копируем список
        res.addAll(fileList);
        //выполняем сортировку
        Collections.sort(res, this);
        //возвращаем результат
        return res;
    }
}

Как видите, значительная часть работы выполнена с помощью стандартных библиотек Java. Так что, можете быть уверены, время, потраченное на их изучение, быстро окупиться.

Если вы захотите изменить способ сортировки, например, сортировать файлы по их размеру, то просто измените метод compare.

Обеспечить поддержку нескольких видов сортировки немного сложнее. Я могу посоветовать использовать следующий метод.

Добавляем несколько констант и одну переменную.

private final int SORT_BY_NAME = 1; //сортировка по имени файла
private final int SORT_BY_SIZE = 2; //сортировка по размеру файла

private int sortType = 1; //в этой переменной сохраняем текущий тип сортировки

Во всех методах задаём тип сортировки:

public List sortByName(List fileList) {
    sortType = SORT_BY_NAME;
    ...
}
public List sortBySize(List fileList) {
    sortType = SORT_BY_SIZE;
    ...
}

А в методе compare добавляем оператор switch и, конечно, все нужные алгоритмы сравнения.

switch (sortType) {
    case SORT_BY_NAME:
    //сравнение объектов по их имени
    case SORT_BY_SIZE:
    //сравнение объектов по их размеру
}

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

Страница: 1 2