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".
启用数字排序后结果为:
禁用数字排序后,它看起来像这样:
这让我觉得 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?"
我正在使用下面的代码来获取文件列表排序:(如 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".
启用数字排序后结果为:
禁用数字排序后,它看起来像这样:
这让我觉得 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?"