
本文介绍一种针对大规模列表(如百万级记录)的智能分区方法,确保相同键值的元素不被拆分到不同子列表中,适用于数据库批量查询时需传递连续键范围的场景。
本文介绍一种针对大规模列表(如百万级记录)的智能分区方法,确保相同键值的元素不被拆分到不同子列表中,适用于数据库批量查询时需传递连续键范围的场景。
在处理海量数据(例如 100 万条 Car 对象)并准备分批调用数据库时,简单按固定大小切分(如每组 6 条)极易导致同一 key(如 Car.key = 2)跨分区出现——这会破坏后续以 [minKey, maxKey] 为条件的范围查询语义,造成数据遗漏或重复。
根本问题在于:固定大小切分无视业务语义,而“键连续性”才是关键约束。因此,正确的解法不是先去重再映射回原列表(如提问者尝试的复杂多步逻辑),而是采用 “语义感知的贪心扩展分区”策略:在保证每组至少 minSize 元素的前提下,自动向后延伸,直至当前块末尾的键与下一元素的键不同。
✅ 推荐实现:排序 + 动态边界扩展
该方案分两步:
- 预排序或预聚类:确保相同键的元素物理相邻;
-
滑动切分 + 键守恒检查:每个分区从起始位置开始,至少容纳
minSize个元素,并持续向后扩展,直到下一个元素的键发生变化。
static <T, K> List<List<T>> partitionByKey(
List<T> data,
int minSize,
Function<T, K> keyFunction) {
// Step 1: 构建带键顺序的副本(稳定聚类)
List<T> sortedCopy = new ArrayList<>(data);
// 若 K 实现 Comparable,直接排序;否则用首次出现顺序聚类
if (data.isEmpty() || keyFunction.apply(data.get(0)) instanceof Comparable) {
sortedCopy.sort(Comparator.comparing(keyFunction));
} else {
// 聚类:为每个新 key 分配递增序号,保持原始相对顺序
Map<K, Integer> keyOrder = new LinkedHashMap<>();
sortedCopy.sort((a, b) -> {
K ka = keyFunction.apply(a), kb = keyFunction.apply(b);
return Integer.compare(
keyOrder.computeIfAbsent(ka, k -> keyOrder.size()),
keyOrder.computeIfAbsent(kb, k -> keyOrder.size())
);
});
}
// Step 2: 动态分区(保证同 key 不跨区)
List<List<T>> result = new ArrayList<>();
for (int start = 0, end; start < sortedCopy.size(); start = end) {
end = Math.min(sortedCopy.size(), start + minSize);
// 扩展至最后一个连续相同 key 的元素
K lastKey = keyFunction.apply(sortedCopy.get(end - 1));
while (end < sortedCopy.size()
&& Objects.equals(lastKey, keyFunction.apply(sortedCopy.get(end)))) {
end++;
}
result.add(Collections.unmodifiableList(sortedCopy.subList(start, end)));
}
return result;
}? 使用示例
record Car(long id, String name, long key) {}
List<Car> cars = List.of(
new Car(1, "Tesla", 1),
new Car(2, "Tesla-3", 2),
new Car(3, "Tesla-Y", 2),
new Car(4, "Tesla-S", 3)
);
List<List<Car>> partitions = partitionByKey(cars, 2, Car::key);
// 输出:
// Partition-1: [Car[1,Tesla,1], Car[2,Tesla-3,2], Car[3,Tesla-Y,2]]
// Partition-2: [Car[4,Tesla-S,3]]⚠️ 注意事项与优化建议
-
性能保障:排序时间复杂度为
O(n log n),但对百万级数据(JVM 中通常 subList +indexOf查找(后者为O(n²)); -
内存友好:不创建中间去重列表或哈希映射,避免额外
O(n)空间开销; -
稳定性:
subList返回的是视图,如需长期持有请显式new ArrayList(...); -
键不可比? 若
key类型未实现Comparable(如自定义对象),使用LinkedHashMap聚类可严格保留首次出现顺序,避免意外重排; -
数据库适配:每个分区调用
repo.callToDb(minKey, maxKey)前,可统一通过partition.stream().map(Car::key).mapToLong(...).summaryStatistics()提取极值,简洁可靠。
该方案以清晰的语义逻辑替代了提问者中冗余的 distinctByKey → partition → getFirstIndexOf → getLastIndexOf 链式操作,在可读性、健壮性与性能上均显著提升,是生产环境处理“键连续分区”需求的标准实践。

















