一、核心结论(先记住这 10 条)
- 默认排序用稳定算法 :List.sort / Collections.sort / Kotlin list.sortWith {} → 底层 TimSort(稳定,适合多字段排序与 UI 列表)。
- 数字比较不要相减:用 Integer.compare(a,b) / Comparator.comparingInt(::field),避免溢出与符号错误。
- 字符串排序面向用户要用 Collator(按语言/Locale 排序),不要用简单 String.compareTo;中英文混排或阿语等尤其明显。
- Null 安全:用 Comparator.nullsFirst/Last 或 Kotlin nullsFirst/Last() 包装比较器。
- 多字段排序:用链式比较器(thenComparing / thenBy),稳定排序可保留同一主键下的原相对顺序。
- 频繁"判断是否包含" :不要每次 list.contains()(O(n)),改用 HashSet/HashMap 预建索引(O(1))。
- 二分查找只适用于已排序区间 :Collections.binarySearch / Kotlin binarySearch,找不到时返回"插入点编码值",要解码 idx = -pos - 1。
- 重复按同一 key 排序:先"装饰-排序-去装饰"(Schwartzian transform)或缓存 key,减少重复计算。
- 大规模实时增删 :不要反复全量排序;用二分插入维持有序,或改 TreeSet/PriorityQueue/数据库排序。
- RecyclerView 变更:排序后用 DiffUtil 计算差异,避免整表闪烁和 O(n) 级刷新。
二、排序(Sorting)
1) 常用 API 与稳定性
-
Java
- list.sort(cmp)(JDK8+)/Collections.sort(list, cmp) → TimSort,稳定,O(n log n),局部有序时更快。
- Arrays.sort(Object[]) → 稳定;Arrays.sort(int[])(原生类型)→ 双轴快速排序,不稳定。
-
Kotlin
-
就地:mutableList.sort() / sortBy { } / sortWith(cmp)(稳定)。
-
返回新列表:sorted()/sortedBy/with(拷贝后排序,适合不可变 Pipeline)。
-
2) 多字段排序(链式比较器)
Java
scss
Comparator<User> cmp = Comparator
.comparing(User::getVip).reversed() // VIP 优先
.thenComparingInt(User::getLevel).reversed() // 等级高在前
.thenComparing(User::getName, Collator.getInstance(Locale.CHINA)); // 中文名
users.sort(cmp);
Kotlin
css
users.sortWith(
compareByDescending<User> { it.vip }
.thenByDescending { it.level }
.thenComparator { a, b -> Collator.getInstance(Locale.CHINA).compare(a.name, b.name) }
)
3) Null 与倒序
less
Comparator<Foo> cmp = Comparator
.comparing(Foo::getScore, Comparator.nullsLast(Integer::compare))
.reversed(); // 全局倒序
Kotlin:nullsLast(naturalOrder()) / reversed()。
4) 字符串的"语言学排序"
用户可见文本排序用:
ini
Collator collator = Collator.getInstance(locale); // e.g. Locale.SIMPLIFIED_CHINESE / ARABIC
list.sort(Comparator.comparing(Item::getTitle, collator));
需要忽略大小写/重音:配置 collator.setStrength(Collator.PRIMARY)。
5) 性能与内存
- TimSort 适合"部分有序" :滚动列表、分页合并、时间线排序性能更佳。
- 装饰-排序-去装饰(Key 缓存) :避免在比较器里反复计算昂贵 key。
kotlin
data class Decor<T>(val key: Int, val v: T)
val decorated = items.map { Decor(calcExpensiveKey(it), it) }
decorated.sortBy { it.key }
val sorted = decorated.map { it.v }
6) 大列表与并行
- Server/JVM 大数据可用 Arrays.parallelSort(对象/原生数组)。Android 端一般不建议并行排序以免与主线程/电量打架;把重活挪到后台线程。
三、查找(Searching)
1) 线性查找(通用)
-
indexOf/lastIndexOf/find:O(n) ,小列表或一次性查找可用。
-
Kotlin 流式:firstOrNull { it.id == target }。
2) 二分查找(已排序)
Java
ini
int idx = Collections.binarySearch(list, target, cmp);
if (idx >= 0) {
// 找到了
} else {
int insertionPoint = -idx - 1; // 未找到,应插入的位置
}
Kotlin
ini
val idx = list.binarySearch(key = 123, comparator = compareBy<Item> { it.id })
-
必须同一比较逻辑 :排序与查找用同一 Comparator,否则结果未定义。
-
稳定插入 :list.add(insertionPoint, x) 实现边查边插维持有序。
3) 高频"包含/去重/按 id 查"
- 预建索引更高效:
javascript
val index: Map<Long, Item> = items.associateBy { it.id } // O(n) 构建,O(1) 查
val set: Set<Long> = items.mapTo(hashSetOf()) { it.id } // O(1) contains
- 需要顺序 + 快查:LinkedHashMap 按插入/访问顺序 + O(1) 查找。
4) 前缀/模糊搜索(UI 实时)
- 小规模:lowerBound/upperBound 在已排序数组上做前缀范围。
- 大规模或频繁:用 Trie / FST / 索引库;或把"搜索"后移到数据库/后端。
四、易踩坑 & 规约
- a - b 比较(可能溢出):
rust
// ❌
(o1, o2) -> o1.score - o2.score
// ✅
Comparator.comparingInt(Foo::getScore)
- 比较器不自洽(非传递/不一致)会导致排序/二分/有序集合失效:
-
保证 自反、反对称、传递,compare(a,b)==0 时尽可能与 equals 一致。
-
需要"稳定分组"时优先 稳定排序 + 明确二级/三级字段。
- 排序后用于二分但 Comparator 不同 → 结果未定义(可能错位/死循环)。
- subList 排序 :subList 是父列表的视图,排序/修改会作用于父列表;跨结构修改可能抛 CME(见 fail-fast 规则)。
- UI 列表闪烁:排序后直接 notifyDataSetChanged() 会整表重绘;用 DiffUtil 或 ListAdapter 计算差异。
五、常用范式代码
1) "时间倒序 + 同时长升序 + 置顶优先"
less
val collator = Collator.getInstance(Locale.getDefault())
items.sortWith(
compareByDescending<Item> { it.pinned } // 置顶
.thenByDescending { it.publishTime } // 时间新
.thenBy { it.durationSec } // 时长短
.thenComparator { a, b -> collator.compare(a.title, b.title) } // 本地化
)
2) 维持有序插入(二分 + 插入点)
kotlin
fun MutableList<Int>.insertSorted(x: Int) {
val pos = binarySearch(x)
val i = if (pos >= 0) pos else -pos - 1
add(i, x)
}
3) 用户可见字符串排序(忽略大小写/重音)
ini
Collator c = Collator.getInstance(locale);
c.setStrength(Collator.PRIMARY); // 忽略大小写与重音
list.sort(Comparator.comparing(Item::getTitle, c));
4) RecyclerView 排序后的增量刷新
css
val diff = DiffUtil.calculateDiff(object: DiffUtil.Callback() { /* ... */ })
items.sortWith(cmp)
diff.dispatchUpdatesTo(adapter)
六、选型与流程建议
-
一次性展示/小数据:sortedBy/sortWith 即可。
-
实时搜索/频繁增删:维持有序(二分插入)或改用 TreeSet/数据库 ORDER BY。
-
用户文本:一律 Collator;需要多语言按 Locale 切换。
-
"是否存在/按 id 找"高频:先建 HashSet/HashMap 索引。
-
端上性能:重活放后台协程;UI 层用 Diff 渐进更新。