如何在 Linux 系统上按字典顺序(不区分大小写)对 file\directory 树进行排序

How to sort file\directory tree in lexicographic order (case-insensitive) on Linux system

我有一个 class 从目录、子目录和文件做树。并且需要满足两个条件。按以下方式对内容进行排序: - 目录应该放在第一位。 - 目录和文件按字典顺序排序(不区分大小写)。 我在 windows 系统上工作,下面的代码工作正常,所有内容都按我想要的方式排序。目录排在第一位,并按字典顺序排序。但我读到 windows 系统自动按字典顺序排序。但是此代码在 Linux 上不起作用,行 children.sort(Comparator.comparing(file -> file.isDirectory() ? -1 : 1)); 不起作用并且 files\directory 未按字典顺序排序(不区分大小写)。如何解决 linux 系统上的排序条件问题?如何更改代码中的排序条件?

import static java.util.Comparator.comparing;

public class TreeNode<T> implements Iterable<TreeNode<T>> {

    public T data;
    public TreeNode<T> parent;
    public List<TreeNode<T>> children;

    public boolean isRoot() {
        return parent == null;
    }

    private List<TreeNode<T>> elementsIndex;

    public TreeNode(T data) {
        this.data = data;
        this.children = new LinkedList<TreeNode<T>>();
        this.elementsIndex = new LinkedList<TreeNode<T>>();
        this.elementsIndex.add(this);
    }

    public TreeNode<T> addChild(T child) {
        TreeNode<T> childNode = new TreeNode<T>(child);
        childNode.parent = this;
        this.children.add(childNode);
        this.registerChildForSearch(childNode);
        return childNode;

    }

    private void registerChildForSearch(TreeNode<T> node) {
        elementsIndex.add(node);
        if (parent != null)
            parent.registerChildForSearch(node);
    }

    @Override
    public String toString() {
        return data != null ? data.toString() : "[data null]";
    }

    @Override
    public Iterator<TreeNode<T>> iterator() {
        TreeNode<T> iter = new TreeNode<T>((T) this);
        return (Iterator<TreeNode<T>>) iter;
    }

    public static TreeNode<File> createDirTree(File folder) {
        if (!folder.isDirectory()) {
            throw new IllegalArgumentException("folder is not a Directory");
        }

       List<File> children = Arrays.asList(folder.listFiles());
children.sort(Comparator.comparing(file -> file.isDirectory() ? -1 : 1));

        TreeNode<File> DirRoot = new TreeNode<File>(folder);
for (File file : children) {
            if (file.isDirectory()) {
                appendDirTree(file, DirRoot);
            } else {
                appendFile(file, DirRoot);
            }
        }
        return DirRoot;
    }

    public static void appendDirTree(File folder, TreeNode<File> dirRoot) {
        dirRoot.addChild(folder);

        List<File> children = Arrays.asList(folder.listFiles());
        children.sort(comparing(file -> file.isDirectory() ? -1 : 1));
for (File file : children) {
            if (file.isDirectory()) {
                appendDirTree(file, dirRoot.children.get(dirRoot.children.size() - 1));
            } else {
                appendFile(file, dirRoot.children.get(dirRoot.children.size() - 1));
            }
        }
    }

    public static void appendFile(File file, TreeNode<File> filenode) {
        filenode.addChild(file);

    }


    public static String renderDirectoryTree(TreeNode<File> tree) {
        List<StringBuilder> lines = renderDirectoryTreeLines(tree);
        String newline = "\n";
        StringBuilder sb = new StringBuilder(lines.size() * 20);
        for (StringBuilder line : lines) {
            sb.append(line);
            sb.append(newline);
        }
        //System.out.println(sb);
        return sb.toString();
    }

    public static List<StringBuilder> renderDirectoryTreeLines(TreeNode<File> tree) {

        List<StringBuilder> result = new LinkedList<>();
        result.add(new StringBuilder().append(tree.data.getName() + " " + calculateFileSize(tree) + " bytes"));
        Iterator<TreeNode<File>> iterator = tree.children.iterator();
        while (iterator.hasNext()) {
            List<StringBuilder> subtree = renderDirectoryTreeLines(iterator.next());
            if (iterator.hasNext()) {
                addSubtree(result, subtree);
            } else {
                addLastSubtree(result, subtree);
            }
        }

        return result;
    }

    private static void addSubtree(List<StringBuilder> result, List<StringBuilder> subtree) {
        Iterator<StringBuilder> iterator = subtree.iterator();
        result.add(iterator.next().insert(0, "├─ "));
        while (iterator.hasNext()) {
            result.add(iterator.next().insert(0, "│  "));
        }
    }

    private static void addLastSubtree(List<StringBuilder> result, List<StringBuilder> subtree) {
        Iterator<StringBuilder> iterator = subtree.iterator();
        result.add(iterator.next().insert(0, "└─ "));
        while (iterator.hasNext()) {
            result.add(iterator.next().insert(0, "   "));
        }
    }

public static long calculateFileSize(TreeNode<File> tree) {
    long fileSize = 0;
    if (tree.data.isDirectory()) {
        List<TreeNode<File>> children = tree.children;


        for (TreeNode<File> child : children) {
            fileSize += calculateFileSize(child);
        }

    } else {
        fileSize = tree.data.length();
    }
    return fileSize;
}

}

一个简短的版本可能看起来像这样。

Comparator<File> lexicographicFileComparator = Comparator.comparing(File::isFile)
    .thenComparing(Comparator.naturalOrder());

你写到 File.compareTo 甚至不检查文件是否存在。我不认为检查文件是否存在是 compareToComparator 的工作。不存在的文件应该在之前过滤。


我认为您的示例过度使用了静态方法。 TreeNode 应该是一个抽象 class,而你有一个名为 FileTreeNode 的实现来替换你的静态方法。

这是一个例子

树节点

public abstract class TreeNode<N extends TreeNode<N, T>, T> implements Iterable<N> {

    private static <N extends TreeNode<N, ?>> Stream<CharSequence> renderBranch(N node) {
        Stream.Builder<CharSequence> result = Stream.builder();
        result.add(node.printNode());
        Iterator<N> iterator = node.getChildren().iterator();
        while (iterator.hasNext()) {
            Stream<CharSequence> subtree = renderBranch(iterator.next());
            Iterator<CharSequence> subtreeIterator = subtree.iterator();
            String branchSplit = "├─ ";
            String branchSpacer = "│  ";
            if (!iterator.hasNext()) {
                branchSplit = "└─ ";
                branchSpacer = "   ";
            }
            result.add(branchSplit + subtreeIterator.next());
            while (subtreeIterator.hasNext()) {
                result.add(branchSpacer + subtreeIterator.next());
            }
        }
        return result.build();
    }

    private final T data;
    private final N parent;

    private final List<N> children;

    public TreeNode(T data) {
        this(data, null);
    }

    protected TreeNode(T data, N parent) {
        this.data = data;
        this.parent = parent;
        children = initChildren();
    }

    /**
     * Called in constructor to initialize the children list.
     */
    protected abstract List<N> initChildren();

    /**
     * Used to avoid unsafe casting.
     *
     * @return This
     */
    protected abstract N instance();

    /**
     * TreeNode knows how to print the tree, but not how to print the current element. This way the child class can decide how it wants to be printed in the tree;
     *
     * @return readable text representation of node.
     * @see TreeNode#renderBranch()
     */
    protected abstract CharSequence printNode();

    /**
     * @return Returns a string representation of the entire branch starting at this node.
     */
    public String renderBranch() {
        Stream<CharSequence> lines = renderBranch(instance());
        return lines.collect(Collectors.joining("\n"));
    }

    /**
     * @return Returns a stream of the entire branch starting at this node
     */
    public Stream<N> streamBranch() {
        return Stream.concat(Stream.of(instance()), getChildren().stream().flatMap(TreeNode::streamBranch));
    }

    public T getData() {
        return data;
    }

    public N getParent() {
        return parent;
    }

    public List<N> getChildren() {
        return children;
    }

    public boolean isRoot() {
        return parent == null;
    }

    @Override
    public String toString() {
        return data != null ? data.toString() : "[data null]";
    }

    @Override
    public Iterator<N> iterator() {
        // No clue what you want to do here, but your method will not work.
        return children.iterator();
    }
}

文件树节点

public class FileTreeNode extends TreeNode<FileTreeNode, File> {

    public FileTreeNode(File root) {
        super(root);
    }

    protected FileTreeNode(File data, FileTreeNode parent) {
        super(data, parent);
    }

    public long calculateFileSize() {
        return streamBranch().mapToLong(value -> value.getData().length()).sum();
    }

    @Override
    protected CharSequence printNode() {
        return getData().getName() + " " + calculateFileSize() + " bytes";
    }

    @Override
    protected List<FileTreeNode> initChildren() {
        File file = getData();
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                return Arrays.stream(files)
                        .sorted(Comparator.comparing(File::isFile).thenComparing(Comparator.naturalOrder()))
                        .map(path -> new FileTreeNode(path, this))
                        .toList();
            }
        }
        return Collections.emptyList();
    }

    @Override
    protected FileTreeNode instance() {
        return this;
    }
}

用法

File file = new File("C:\dir");
FileTreeNode rootNode = new FileTreeNode(file);
System.out.println(rootNode.renderBranch());

这样您也可以轻松地为其他 class 实现 TreeNode 结构。例如 File 已过时并被 Path 取代,因此您将来可能需要 PathTreeNode。谁知道呢