创建文件体系结构的最小mkdirs数

时间:2017-06-01 15:40:10

标签: java performance collections

假设我有一个List个路径,我想减少它,以便运行最小数量的file.mkdirs()来重新创建整个架构。

因此,来自:

  

[/ foo,/ foo / bar,/ foo / bar / coo,/ foo / bar / coo2,/ foo / barbie,/ notFoo / something]

我期待:

  

[/ notFoo / something,/ foo / barbie,/ foo / bar / coo,/ foo / bar / coo2]

我做到这一点的天真方式是:

List<String> l_paths = Arrays.asList("/foo","/foo/bar", "/foo/bar/coo","/foo/barbie","/notFoo/something");
    ArrayList<String> l_reducted = new ArrayList<>();
    List<String> l_ordered = l_paths.stream().sorted((p1,p2) -> p2.compareTo(p1)).collect(Collectors.toList());
    for(String l_string : l_ordered){
        if(l_reducted.stream().noneMatch(e -> e.startsWith(l_string) && e.substring(l_string.length()).contains("/"))){
            l_reducted.add(l_string);
        }
    }
    System.out.println(l_reducted);

或者,对于java 8爱好者:

// java 8 style, way less readable IMO
    BiFunction<List<String>, String, List<String>> myAccumulator = new BiFunction<List<String>, String, List<String>>() {
        @Override
        public List<String> apply(List<String> list, String string) {
            if (list.stream().noneMatch(e -> e.startsWith(string) && e.substring(string.length()).contains("/"))) {
                list.add(string);
            }
            return list;
        }
    };
    System.out.println(l_paths.stream().sorted((p1, p2) -> p2.compareTo(p1))
            .reduce(new ArrayList<>(),
                    myAccumulator, 
                    (list1, list2) -> {
                        list2.stream().forEach(i -> myAccumulator.apply(list1, i));
                        return list1;
                    }));

但是我非常确信将分隔符上的每个路径分开并将它们插入类似于文件系统的树状结构中会更好(但我不熟悉树木,所以我没有&# 39; t实现它,因为它允许只访问节点和mkdir我的方式。

您认为哪种更好?

免责声明:我不是真的在这里讨论过早优化,我只是对算法感兴趣,因为知识好奇心。但是,让我们说mkdir实际上是对一个非常慢的Web服务的调用(它甚至不能理解整个路径上的mkdirs)并且调用的数量很重要。而且我们还会假设我的收藏中有数百万条路径,而且缩减的计算复杂性也很重要。

2 个答案:

答案 0 :(得分:2)

将此视为一项学术活动,而不是同意减少对mkdirs()的调用是值得追求的......

  1. 按字母顺序对列表进行排序
  2. 使用String[]
  3. 将每个字符串映射到path.split("/")
  4. 遍历列表。如果当前条目不以上一个条目的所有元素开头,则输出前一个条目。
  5. 最后输出最后看到的条目(假设输入列表不为空)
  6. 类似的东西:

     List<String[]> sortedPaths = paths.stream().sorted().map( s -> s.split("/"))
    
     List<String> out = new ArrayList<>();
     String[] previous = new String[0];
    
     for(String[] path : sortedPaths) {
         if(! beginsWith(path,previous)) {
              out.add(String.join(",", previous));
         }
         previous = path;
     }
     out.add(String.join(",", previous));
    

    我将beginsWith(String[], String[])的实现留给读者,并在需要时处理空输入列表。

    或者,仍然按字母顺序排序:

      for(String path : paths) {
          if(out.isEmpty() || ! isSubPath(out.get(out.size()-1), path) {
              out.add(path);
          } else {
              out.set(out.size()-1, path);
          }
      } 
    

    isSubPath测试第一个参数是否与第二个参数具有相同的父目录

    请注意,如果您尝试保存文件系统调用:

     mkdirs("/a/b/c/d");
     mkdirs("/a/b/e/f");
    

    ...仍在执行超出严格必要的系统调用,因为在mkdirs()后面是一堆mkdir(),并且它会尝试创建/a/a/b两次。

    如果您对减少文件系统操作感到狂热(这可能是值得的,例如在远程服务的慢速链接上),您可能希望:

    • 将您的路径列表展开为单个mkdir()列表 - 即{"a/b/c"}变为{"a", "a/b", "a/b/c"}
    • 排序和删除重复项
    • mkdir()每个人。

答案 1 :(得分:0)

  

但我非常确信在分隔符上拆分每条路径   并将它们插入类似于文件系统的树结构中   会更好(但我不熟悉树木,所以我没有   实现它)因为它将允许只访问节点和   mkdir我的方式。

您当然可以使用类似Trie的基于树的数据结构来解决问题,每个节点对应一个路径段。如果您在这样的数据结构中记录所有路径,那么您可以找到导致创建整个层次结构所需的最小集合 - 它正是与叶节点对应的那些。

但是编写数据结构的代码要做很多工作。只有你继续使用它才会对我有意义。如果您需要做的只是确定(假设的)特里的叶节点,那么你可以通过@slim建议的方法非常干净和有效地完成它。