Java File list same order like Window explorer

血红的双手。 提交于 2020-02-25 02:50:26

问题


I am using the code below to get file list ordering: (like window explorer)

    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, output when I run the program:

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

Expected Output (Like window explorer):

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

What to do to get file list like this?

UPDATED

Implemented solution provided by @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


回答1:


Sorting by name in Windows is tricky and far more complicated than your implementation. It's also configurable and version dependent.

NOTE: I created a demo for what follows in this post. Check it out on GitHub.

Sorting file names using StrCmpLogicalWComparator function

According to some (e.g. here) Windows uses StrCmpLogicalW for sorting files by name.

You could try to implement your comparator by calling this system function using JNA (don't forget to include JNA library in your project):

Comparator:

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 part:

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

Handling file names that contain digits

I mentioned earlier that the way that Windows Explorer sorts files is configurable. You can change how numbers in file names are handled and toggle so-called "numerical sorting". You can read how to configure this here. Numerical sorting as explained in the docs:

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

With numerical sorting enabled the result is:

whereas with numerical sorting disabled it looks like this:

This makes me think that Windows Explorer in fact uses CompareStringEx function for sorting which can be parameterized to enable this feature.

Sorting file names using CompareStringEx function

JNA part:

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

Numeric sort comparator:

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

Non-numeric sort comparator:

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

References

  • 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?"


来源:https://stackoverflow.com/questions/60092486/java-file-list-same-order-like-window-explorer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!