Java 文件列表与 Window 资源管理器的顺序相同

Java File list same order like Window explorer

我正在使用下面的代码来获取文件列表排序:(如 window 资源管理器)

    package com.codnix.quickpdfgenerator.testing;
    import java.io.File;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.Iterator;
    import java.util.List;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;

    public class FileListOrder {
        public static void main(String args[]) {
            //huge test data set ;)

            File folder = new File("C:\Users\Codnix\Desktop\Test Sequence");
            File[] listOfFiles = folder.listFiles();
            List<File> filenames = Arrays.asList(listOfFiles); 

            //adaptor for comparing files
            Collections.sort(filenames, new Comparator<File>() {
                private final Comparator<String> NATURAL_SORT = new WindowsExplorerComparator();

                @Override
                public int compare(File o1, File o2) {;
                    return NATURAL_SORT.compare(o1.getName(), o2.getName());
                }
            });

            for (File f : filenames) {
                System.out.println(f);
            }
        }

        public static class WindowsExplorerComparator implements Comparator<String> {

            private static final Pattern splitPattern = Pattern.compile("\d+|\.|\s");

            @Override
            public int compare(String str1, String str2) {
                Iterator<String> i1 = splitStringPreserveDelimiter(str1).iterator();
                Iterator<String> i2 = splitStringPreserveDelimiter(str2).iterator();
                while (true) {
                    //Til here all is equal.
                    if (!i1.hasNext() && !i2.hasNext()) {
                        return 0;
                    }
                    //first has no more parts -> comes first
                    if (!i1.hasNext() && i2.hasNext()) {
                        return -1;
                    }
                    //first has more parts than i2 -> comes after
                    if (i1.hasNext() && !i2.hasNext()) {
                        return 1;
                    }

                    String data1 = i1.next();
                    String data2 = i2.next();
                    int result;
                    try {
                        //If both datas are numbers, then compare numbers
                        result = Long.compare(Long.valueOf(data1), Long.valueOf(data2));
                        //If numbers are equal than longer comes first
                        if (result == 0) {
                            result = -Integer.compare(data1.length(), data2.length());
                        }
                    } catch (NumberFormatException ex) {
                        //compare text case insensitive
                        result = data1.compareToIgnoreCase(data2);
                    }

                    if (result != 0) {
                        return result;
                    }
                }
            }

            private List<String> splitStringPreserveDelimiter(String str) {
                Matcher matcher = splitPattern.matcher(str);
                List<String> list = new ArrayList<String>();
                int pos = 0;
                while (matcher.find()) {
                    list.add(str.substring(pos, matcher.start()));
                    list.add(matcher.group());
                    pos = matcher.end();
                }
                list.add(str.substring(pos));
                return list;
            }
        } 
    }

BUT,当我运行程序输出:

C:\Users\Codnix\Desktop\Test Sequence test -12.jpg
C:\Users\Codnix\Desktop\Test Sequence test --11.jpg
C:\Users\Codnix\Desktop\Test Sequence test ---10.jpg

预期输出(如 window 资源管理器):

C:\Users\Codnix\Desktop\Test Sequence test ---10.jpg
C:\Users\Codnix\Desktop\Test Sequence test --11.jpg
C:\Users\Codnix\Desktop\Test Sequence test -12.jpg

如何获取这样的文件列表?

已更新

@jannis

提供的实现方案
And here its output

before

1 test ---10.jpg
1 test --11.jpg
1 test -12.jpg
1.jpg
10.jpg
2.jpg

After (output)

1.jpg
1 test ---10.jpg
1 test --11.jpg
1 test -12.jpg
2.jpg
10.jpg

预期

在 Windows 中按名称排序是棘手的,并且比您的实现复杂得多。它也是可配置的并且依赖于版本。

注意:我为下面的内容创建了一个演示 post。 Check it out on GitHub.

使用 StrCmpLogicalWComparator function

对文件名进行排序

根据一些(例如 here) Windows uses StrCmpLogicalW 按名称排序文件。

您可以尝试使用 JNA 调用此系统函数来实现您的比较器(不要忘记在您的项目中包含 JNA library):

比较器:

public class StrCmpLogicalWComparator implements Comparator<String> {

    @Override
    public int compare(String o1, String o2) {
        return Shlwapi.INSTANCE.StrCmpLogicalW(
            new WString(o1), new WString(o2));
    }
}

JNA 部分:

import com.sun.jna.WString;
import com.sun.jna.win32.StdCallLibrary;

public interface Shlwapi extends StdCallLibrary {

    Shlwapi INSTANCE = Native.load("Shlwapi", Shlwapi.class);

    int StrCmpLogicalW(WString psz1, WString psz2);
}

处理包含数字的文件名

我之前提到 Windows Explorer 对文件进行排序的方式是可配置的。您可以更改文件名中数字的处理方式并切换所谓的 "numerical sorting"。您可以阅读如何配置此 here。文档中解释的数字排序:

Treat digits as numbers during sorting, for example, sort "2" before "10".

-- https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-comparestringex#SORT_DIGITSASNUMBERS

启用数字排序后结果为:

禁用数字排序后,它看起来像这样:

这让我觉得 Windows Explorer 实际上使用 CompareStringEx function 进行排序,可以对其进行参数化以启用此功能。

使用 CompareStringEx function

对文件名进行排序

JNA 部分:

import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.win32.StdCallLibrary;

public interface Kernel32 extends StdCallLibrary {

    Kernel32 INSTANCE = Native.load("Kernel32", Kernel32.class);
    WString INVARIANT_LOCALE = new WString("");

    int CompareStringEx(WString lpLocaleName,
                        int dwCmpFlags,
                        WString lpString1,
                        int cchCount1,
                        WString lpString2,
                        int cchCount2,
                        Pointer lpVersionInformation,
                        Pointer lpReserved,
                        int lParam);

    default int CompareStringEx(int dwCmpFlags,
                                String str1,
                                String str2) {
        return Kernel32.INSTANCE
            .CompareStringEx(
                INVARIANT_LOCALE,
                dwCmpFlags,
                new WString(str1),
                str1.length(),
                new WString(str2),
                str2.length(),
                Pointer.NULL,
                Pointer.NULL,
                0);
    }
}

数字排序比较器:

public class CompareStringExNumericComparator implements Comparator<String> {

    private static int SORT_DIGITSASNUMBERS = 0x00000008;

    @Override
    public int compare(String o1, String o2) {
        int compareStringExComparisonResult =
            Kernel32.INSTANCE.CompareStringEx(SORT_DIGITSASNUMBERS, o1, o2);

        // CompareStringEx returns 1, 2, 3 respectively instead of -1, 0, 1
        return compareStringExComparisonResult - 2;
    }
}

非数字排序比较器:

public class CompareStringExNonNumericComparator implements Comparator<String> {

    private static String INVARIANT_LOCALE = "";
    private static int NO_OPTIONS = 0;

    @Override
    public int compare(String o1, String o2) {
        int compareStringExComparisonResult =
            Kernel32.INSTANCE.CompareStringEx(NO_OPTIONS, o1, o2);

        // CompareStringEx returns 1, 2, 3 respectively instead of -1, 0, 1
        return compareStringExComparisonResult - 2;
    }
}

参考资料

  • Martin Liversage's answer to "What is the shortest way in .NET to sort strings starting with 1, 10 and 2 and respect the number ordering?
  • hmuelner's answer to "What is the first character in the sort order used by Windows Explorer?"