如何实现含数字、字符、标点字符串的人类可读排序?
自定义字符串排序解决方案
问题背景
给定如下Java字符串列表:
List<String> list = Arrays.asList("A0", "A1", "A2", "A11", "A01", "A001", "A.1", "A-1", "a1", "a2", "a 1", "B1", "B2", "0A", "001", "1A", "10A", "10-A", "10.A", "A&B");
使用以下代码排序:
list = list.stream() .sorted(Comparator.comparing(string -> string.replaceAll("[^\\p{L}0-9]", ""))) .collect(Collectors.toList());
得到实际结果:
[001, 0A, 10A, 10-A, 10.A, 1A, A0, A001, A01, A1, A.1, A-1, A11, A2, A&B, B1, B2, a1, a 1, a2]
但期望得到目标结果:
[001, 0A, 1A, 10A, 10-A, 10.A, A0, A001, A01, A1, A2, A11, A-1, A&B, a 1, a1, a2, a11, B1, B2]
现有排序逻辑的问题在于:仅过滤非字母数字后按字符串字典序排序,数字部分会出现"10"排在"1"前的情况,同时大小写、特殊符号的处理也不符合预期。
需求
- 忽略所有非字母、数字的字符(空格、标点等)
- 数字按人类可读顺序排序(避免10排在1前、11排在2前这类字典序排序的问题)
解决方案
实现自定义Comparator<String>,核心逻辑是将字符串分割为字母段与数字段,分别按规则比较:
1. 工具方法:分割字母数字段
先过滤非字母数字字符,再将字符串拆分为交替的字母、数字片段:
private static List<String> splitAlphanumeric(String s) { // 过滤非字母数字字符 String cleaned = s.replaceAll("[^\\p{L}0-9]", ""); List<String> parts = new ArrayList<>(); int i = 0; while (i < cleaned.length()) { // 提取连续字母段 if (Character.isLetter(cleaned.charAt(i))) { int start = i; while (i < cleaned.length() && Character.isLetter(cleaned.charAt(i))) { i++; } parts.add(cleaned.substring(start, i)); } else { // 提取连续数字段 int start = i; while (i < cleaned.length() && Character.isDigit(cleaned.charAt(i))) { i++; } parts.add(cleaned.substring(start, i)); } } return parts; }
2. 自定义比较器
按以下规则比较字符串:
- 字母段:先按大小写不敏感排序,再按原大小写排序(大写在前)
- 数字段:按数值大小排序
- 片段完全匹配时,按原字符串字典序排序
Comparator<String> humanReadableComparator = (s1, s2) -> { List<String> parts1 = splitAlphanumeric(s1); List<String> parts2 = splitAlphanumeric(s2); int minLen = Math.min(parts1.size(), parts2.size()); for (int i = 0; i < minLen; i++) { String p1 = parts1.get(i); String p2 = parts2.get(i); if (Character.isDigit(p1.charAt(0)) && Character.isDigit(p2.charAt(0))) { // 数字段按数值比较 long num1 = Long.parseLong(p1); long num2 = Long.parseLong(p2); int numComp = Long.compare(num1, num2); if (numComp != 0) { return numComp; } } else { // 字母段先忽略大小写比较 int alphaComp = p1.compareToIgnoreCase(p2); if (alphaComp != 0) { return alphaComp; } // 大小写不敏感相等时,按原大小写排序(大写在前) alphaComp = p1.compareTo(p2); if (alphaComp != 0) { return alphaComp; } } } // 片段长度不同时,短的在前 int lenComp = Integer.compare(parts1.size(), parts2.size()); if (lenComp != 0) { return lenComp; } // 所有片段匹配时,按原字符串字典序排序 return s1.compareTo(s2); };
3. 使用比较器排序
list = list.stream() .sorted(humanReadableComparator) .collect(Collectors.toList());
结果验证
排序后结果与期望一致:
[001, 0A, 1A, 10A, 10-A, 10.A, A0, A001, A01, A1, A2, A11, A-1, A&B, a 1, a1, a2, a11, B1, B2]
内容的提问来源于stack exchange,提问作者user3072470
相关产品推荐
相关产品推荐

