【问题标题】:How to sort List<File> to list directories first and grouping files by directory?如何排序 List<File> 以首先列出目录并按目录对文件进行分组?
【发布时间】:2015-08-31 13:42:43
【问题描述】:

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

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&lt;File&gt; 中的项目顺序在不同的操作系统中有所不同,因此我将根据以下规则对它们(即包装列表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

【问题讨论】:

  • 按文件的完整路径名对文件进行排序应该可以解决问题,不是吗?
  • @ammoQ:很遗憾,没有,因为例如/path/to/dir/README.txt 会小于/path/to/dir/second/utils.h,如果我按它们的完整路径名对它们进行排序。
  • 嗯,这取决于您是否将README.txt 视为dir 节点的叶子节点,该节点位于second 节点之前。如果你想首先拥有目录,它与深度优先不同,确切地说。
  • 您可以在 apache-common-io lib 中使用以下比较器之一:commons.apache.org/proper/commons-io/apidocs/org/apache/commons/…。尝试 DefaultFileComparator 或 DirectoryFileComparator 或 PathFileComparator 或其中少数的复合比较器 - commons.apache.org/proper/commons-io/apidocs/org/apache/commons/…
  • 所以在你的比较例程中,按路径(不带文件名)排序,如果路径相等,则按文件名。

标签: java algorithm sorting apache-commons comparator


【解决方案1】:

您可以使用 Java 8 的 streams 来解决您的问题。像这样的东西应该可以工作:

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

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

【讨论】:

    【解决方案2】:

    只是打字的问题;下面我不采用规范路径(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 只比较比较器,而不是文件。

    【讨论】:

      【解决方案3】:

      经过一下午的摸索,这对我有用:

      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 字符。这样,库存字符串排序会将所有子文件夹推到每个文件夹的顶部。只要您没有任何以空字符开头的文件名,它就应该可以正常工作,由于旧 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,这个答案都能满足你。

      【讨论】:

      • 问题是 5 岁。我认为 nio 解决方案是 2020 年更好的方法。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多