java - Java 文件列表与 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;
}
}
}
但是,当我运行程序时输出:
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
怎么做才能得到这样的文件列表?
更新
@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 中按名称排序很棘手,而且比您的实现要复杂得多。它也是可配置的和版本相关的。
注意:我为本文后面的内容创建了一个演示。在 GitHub 上查看。
使用StrCmpLogicalWComparator 函数对文件名进行排序
根据一些(例如这里)Windows 使用StrCmpLogicalW按名称对文件进行排序。
您可以尝试通过使用 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 资源管理器对文件进行排序的方式是可配置的。您可以更改文件名中数字的处理方式并切换所谓的“数字排序”。您可以在此处阅读如何配置它。文档中解释的数字排序:
排序时将数字视为数字,例如将“2”排在“10”之前。
启用数字排序后,结果为:
而在禁用数字排序的情况下,它看起来像这样:
这让我觉得 Windows 资源管理器实际上使用CompareStringEx 函数进行排序,可以参数化以启用此功能。
使用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;
}
}
参考
推荐阅读
- r - 获取 sample() 函数的 data.table 子集的正确行数
- actions-on-google - Google Home 应用中缺少锁定/解锁操作/按钮
- java - 使用 Maven/POM.xml 将在“target/bla/bla/bla”中生成的文件夹移动到“src\main\java\com\demo\project”
- python - 在 Django 中使用 whois
- gremlin - AWS 海王星 Gremlin 综合指数
- python - 解决 Errno 13 权限被拒绝
- python - 使用 Python 和 Notepad++ Unicode 格式的文本文件批量换词
- node.js - 使用 Google KMS 的 Node JS 中的 PDFNet 数字签名
- c# - 无法在 C#.net 中的模型类中绑定数据,将动态数据绑定到字段时出错
- sql-server - 在 VBA 中创建 SQL 视图