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

2024-04-10

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

    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\1 test -12.jpg
C:\Users\Codnix\Desktop\Test Sequence\1 test --11.jpg
C:\Users\Codnix\Desktop\Test Sequence\1 test ---10.jpg

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

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

怎样才能得到这样的文件列表?

UPDATED

@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

Expected


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

注意:我为本文中的后续内容创建了一个演示。在 GitHub 上查看 https://github.com/jannis-baratheon/stackoverflow--sorting-with-winapi.

使用排序文件名StrCmpLogicalW 比较器函数 https://learn.microsoft.com/pl-pl/windows/win32/api/shlwapi/nf-shlwapi-strcmplogicalw

根据一些人的说法(例如here https://stackoverflow.com/questions/4290541/what-is-the-first-character-in-the-sort-order-used-by-windows-explorer/4314191#4314191) Windows 使用StrCmp逻辑W https://learn.microsoft.com/pl-pl/windows/win32/api/shlwapi/nf-shlwapi-strcmplogicalw用于按名称对文件进行排序。

您可以尝试使用 JNA 调用此系统函数来实现比较器(不要忘记包括JNA图书馆 https://mvnrepository.com/artifact/net.java.dev.jna/jna在你的项目中):

比较器:

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 资源管理器对文件进行排序的方式是可配置的。您可以更改文件名中数字的处理方式并切换所谓的“数字排序”。您可以阅读如何配置它here https://www.tenforums.com/tutorials/91417-enable-disable-numerical-sorting-file-explorer-windows-10-a.html。文档中解释的数字排序:

排序时将数字视为数字,例如将“2”排在“10”之前。

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

启用数字排序后,结果为:

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

这让我认为 Windows 资源管理器实际上使用比较字符串Ex函数 https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-comparestringex用于排序,可以对其进行参数化以启用此功能。

使用排序文件名比较字符串Ex函数 https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-comparestringex

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;
    }
}

参考

  • 马丁·利弗西奇 https://stackoverflow.com/users/98607/martin-liversage 回答“.NET 中对以 1、10 和 2 开头的字符串进行排序并遵循数字顺序的最短方法是什么? https://stackoverflow.com/a/7205788/4494577
  • 赫穆尔纳的 https://stackoverflow.com/users/324065/hmuelner 回答“Windows 资源管理器使用的排序顺序中的第一个字符是什么?” https://stackoverflow.com/a/4314191/4494577
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐