对文本文件中的行子组进行排序

Sort subgroups of lines in text file

我有一个名为 filelist.txt 的服务器 属性 文件。此文件列表包含文件列表,这些文件根据目录前缀分组排列。内容的顺序很重要,因为文件必须以特定的文件名开始和结束,并且第一个子组必须首先出现。例如,文件如下所示:

config.txt
../../linux/a.txt
../../linux/c.txt
../../linux/d.txt
../../linux/b.txt
../../certificates/../../d.txt
../../certificates/../../a.txt
../../certificates/../../c.txt
../../certificates/../..b.txt
../../bin/b.txt
../../bin/a.txt
properties.server

我想知道的是,以干净高效的方式在这些子组中进行排序,同时保持子组的整体顺序的最佳方法是什么?

我写了这段代码,它可以按子组过滤并对其进行排序:

try(Stream<String> lines = Files.lines(Paths.get("src/filelist.txt"))){

            List<String> linez = lines.filter(l -> l.contains("linux")).sorted().collect(Collectors.toList());

            BufferedWriter bw = new BufferedWriter(new FileWriter("src/output.txt"));

            for(String line : linez){
                bw.write(line+"\n");
            }

            bw.close();

我可以有一个 List<String> 来包含我所有的行,我可以过滤原始文件行,对它们进行排序,然后将它们添加到这个列表中。

有几件事我不喜欢:

  1. 我不是在覆盖原始文件,而是在写入一个新文件。我想看看有没有办法覆盖而不是写入新文件。
  2. 好像有点迟钝。如果添加了新的目录前缀怎么办?然后我将不得不再次编辑此代码以过滤和排序新的目录前缀组。此外,根据每个过滤器为每个子组制作一堆不同的列表,而不是像 lines.filter(...).sort().filter(...).sort().filter(...) 这样的事情,会感觉很奇怪,但我认为这种语法还没有意义,因为一旦过滤器是已应用,排序后我无法取消应用过滤器。如果能够过滤、就地排序、然后应用另一个过滤器并排序等等,那将是非常好的。

我有哪些选择?

流不是最好的方法(至少不是对整个工作;也许对部分工作)。下面使用查看每一行的方法,如果到最后一个组件的路径与之前读取的相同,则将其添加到列表中,如果不相同,则对该路径列表进行排序,然后将它们添加到排序后的结果,使得每个前缀组的顺序不变,但每个组最终排序。

覆盖输入文件只是使用同一个文件进行读取和写入的问题 - 只需确保在打开它进行写入之前读取所有内容即可。下面只是在处理文件之前将文件读入列表。

import java.util.ArrayDeque;
import java.nio.file.Path;
import java.nio.file.Files;
import java.io.IOException;
import java.io.PrintWriter;

public class Demo {
    public static void main(String[] args) {
        try {
            Path datafile = Path.of(args[0]);
            var lines = Files.readAllLines(datafile);
            var sorted = new ArrayDeque<String>(lines.size());

            // Add first line
            sorted.addLast(lines.get(0));

            // Iterate through all the remaining but the last line
            var block = new ArrayDeque<Path>();
            for (int i = 1; i < lines.size() - 1; i++) {
                Path p = Path.of(lines.get(i));
                if (!block.isEmpty()
                    && !p.getParent().equals(block.getLast().getParent())) {
                    // Current path has a different prefix than the current
                    // block. Sort block and add it to output
                    block.stream()
                        .sorted()
                        .forEachOrdered(sp ->
                                        sorted.addLast(sp.toString()));
                    // And reset for a new path prefix
                    block.clear();
                }
                block.addLast(p);
            }
            // Handle the last block of paths
            block.stream()
                .sorted()
                .forEachOrdered(sp -> sorted.addLast(sp.toString()));

            // Add last line
            sorted.addLast(lines.get(lines.size() - 1));

            // Overwrite the original input file
            Files.write(datafile, sorted);
        } catch (IOException e) {
            System.err.println(e);
            System.exit(1);
        }
    }
}

这基于,但通过直接在原始列表中对受影响的组进行排序来简化操作。

try {
    Path datafile = Path.of(args[0]);
    var lines = Files.readAllLines(datafile);

    // ensure that the list is mutable
    if(lines.getClass() != ArrayList.class) lines = new ArrayList<>(lines);

    int first = 1; // skip first line
    int last = lines.size() - 1; // and last line

    if(first >= last) return;

    Path previous = Path.of(lines.get(first));
    for (int i = first + 1; i < last; i++) {
        Path p = Path.of(lines.get(i));
        if(!p.getParent().equals(previous.getParent())) {
            lines.subList(first, i).sort(null);
            first = i;
            previous = p;
        }
    }
    // Handle the last block of paths
    if(first < last) lines.subList(first, last).sort(null);

    // Overwrite the original input file
    Files.write(datafile, lines);
} catch (IOException e) {
    System.err.println(e);
    System.exit(1);
}

请注意,Files.lines 返回的列表类型未指定,但实际上总是 ArrayList。该解决方案保护自己免受返回列表不可变的假设情况的影响,但在现实生活中不执行复制操作。所以它既形式上正确又有效。