如何排序List <file>首先列出目录并按目录分组文件?

时间:2015-08-31 13:42:43

标签: java algorithm sorting apache-commons comparator

为了获取指定目录中包含的所有文件并根据某些扩展名,我使用listFiles库中FileUtilsApache Commons IO的方法,如以下代码示例。

ArrayList<String> wildcards = new ArrayList<>();
wildcards.add("*.cpp");
wildcards.add("*.h");
wildcards.add("*.txt");

File dir = new File("/path/to/dir");
Collection<File> found = FileUtils.listFiles(
        dir,
        new WildcardFileFilter(wildcards, IOCase.SENSITIVE),
        DirectoryFileFilter.DIRECTORY);

List<File> files = new ArrayList<>(found);

结果Collection<File>中的项目顺序在不同的操作系统中有所不同,因此我会根据以下规则对它们进行排序(即包装列表files)。

  • 目录应列在文件之前。
  • 排序例程应按目录分组文件。

示例:

/path/to/dir/first/subpath/main.cpp
/path/to/dir/first/subpath/utils.cpp
/path/to/dir/first/subpath/utils.h
/path/to/dir/first/main.cpp
/path/to/dir/first/utils.cpp
/path/to/dir/first/utils.h
/path/to/dir/second/main.cpp
/path/to/dir/second/utils.cpp
/path/to/dir/second/utils.h
/path/to/dir/README.txt

3 个答案:

答案 0 :(得分:3)

您可以使用Java 8的stream来解决您的问题。这样的事情应该有效:

//untested
Map<Path, List<Path>> dirToFileMap = files.stream()
            .map(f -> Paths.get(f.getAbsolutePath()))
            .collect(Collectors.groupingBy(Path::getParent));

使用该地图,您可以实现所需。例如,首先迭代keySet

答案 1 :(得分:1)

只是打字的问题;下面我不采用规范路径(Windows不区分大小写),但要点很清楚。

所以Comparator用于排序:

public class FileComparator extends Comparator<File> {

    @Override
    public int compareTo(File lhs, File rhs) {
        if (lhs == null && rhs == null) {
            return 0;
        } else if (lhs == null || rhs == null) {
            return lhs == null ? -1 : 1;
        }
        int cmp = compareTo(lhs.getParentFile(), rhs.getParentFile());
        if (cmp == 0) {
            if (lhs.isDirectory() != rhs.isDirectory()) {
                return lhs.isDirectory() ? -1 : 1;
            }
            cmp = lhs.getName().compareTo(rhs.getName());
        }
        return cmp;
    }

    @Override
    public boolean equals(Object comparator) {
        return comparator != null && comparator.getClass().equals(getClass());
    }
}

equals一样,只比较比较器,而不是文件。

答案 2 :(得分:1)

整个下午都为此挠头之后,这是对我有用的方法:

files.sort((p1, p2) -> pathToStr(p1).compareToIgnoreCase(pathToStr(p2)));
files.forEach(System.out::println); //They'll be ordered now!

//A method I wrote for pulling this off
public static String pathToStr(File path) {
    boolean isDir = path.isDirectory();
    String str = path.getAbsolutePath().replace("\\", "/").replace("/", "/\0");
    if (!isDir) {
        int idx = str.lastIndexOf("/\0");
        str = str.substring(0, idx) + str.substring(idx, str.length()).replace("/\0", "/");
    }
    return str;
}

因此,基本上pathToStr()所做的是对文件路径进行字符串表示,但有一点曲折:路径中的所有文件夹名称都将以空\0字符开头。这样,股票字符串排序会将所有子文件夹推到每个文件夹的顶部。只要您没有以null char开头的任何文件名,它就可以正常工作,由于旧C语言处理字符串的能力很好,我什至认为这是不可能的。

另外要注意的是,如果您想以更类似于Unix的方式订购商品,则可以将compareToIgnoreCase更改为compareTo,以考虑不同的大小写。

这个问题可能有点老(我写这篇文章的时候已经5岁了),但是我找不到任何不涉及使用库或混合多个比较器的答案,所以一旦我弄清楚了我决定回到这里,加上我的两分钱。享受您保存的下午。

编辑:由于该问题未使用NIO路径,因此我不得不仅使用File将代码更改为备用版本;但是如果您正在使用像我这样的NIO路径,这是我正在使用的代码:

List<Path> items = Files.walk(rootFolder).collect(Collectors.toList());
items.sort((p1, p2) -> pathToStr(p1).compareToIgnoreCase(pathToStr(p2)));
items.forEach(System.out::println); //They'll be ordered now!

public static String pathToStr(Path path) {
    boolean isDir = Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS);
    String str = path.toAbsolutePath().toString().replace("\\", "/").replace("/", "/\0");
    if (!isDir) {
        int idx = str.lastIndexOf("/\0");
        str = str.substring(0, idx) + str.substring(idx, str.length()).replace("/\0", "/");
    }
    return str;
}

无论您是否使用NIO,我们都可以解决。