问题记录:
java
/**
* 批量装入滚动计算结果特征值信息
* @param rollResultEigList 滚动结果列表
*/
public void putRollEigResults(List<ResourceCalcSceneSectionResultEig> rollResultEigList) {
if (rollResultEigList == null || rollResultEigList.isEmpty()) {
return;
}
if (rollEigResults == null) {
rollEigResults = new LinkedHashMap<>();
}
// 按照scprodId分组,只取第一条记录
rollResultEigList.stream()
// 假设ResourceCalcSceneSectionResultEig有一个getScprodId()方法
.collect(Collectors.toMap(
ResourceCalcSceneSectionResultEig::getScprodId, // key: scprodId
rollResultEig -> rollResultEig, // value: 对象本身
(existing, replacement) -> existing, // 如果key重复,保留第一个
LinkedHashMap::new // 保持插入顺序的Map
))
.forEach((scprodId, rollResultEig) -> {
rollEigResults.put(scprodId, rollResultEig);
});
}
按照以上这个代码,我在数据库里面查询后的结果rollResultEigList,是按照scprodId字段降序desc排列的,为什么经过这个putRollEigResults函数变成了scprodId升序排列呢?
原因分析:
问题出在 LinkedHashMap 的插入顺序上。当使用 Collectors.toMap(..., LinkedHashMap::new) 时,它确实会保持插入顺序,但是 rollResultEigList 在经过 toMap 处理后,顺序会发生变化,因为:
HashMap的key遍历顺序:在 toMap 过程中,流会遍历 rollResultEigList 并按 scprodId 分组
合并逻辑导致顺序变化:当遇到重复的 scprodId 时,合并函数 (existing, replacement) -> existing 会保留第一个遇到的,但 HashMap的key在插入时会重新排序(虽然LinkedHashMap保持插入顺序,但key的插入顺序可能不同于原列表顺序)
更重要的是,Collectors.toMap 在处理过程中不保证保留原列表的顺序。如果想保持原列表的降序排列,需要手动处理:
java
/**
* 批量装入滚动计算结果特征值信息
* @param rollResultEigList 滚动结果列表
*/
public void putRollEigResults(List<ResourceCalcSceneSectionResultEig> rollResultEigList) {
if (rollResultEigList == null || rollResultEigList.isEmpty()) {
return;
}
if (rollEigResults == null) {
rollEigResults = new LinkedHashMap<>();
}
// 使用LinkedHashMap来保持插入顺序
// 但是我们需要按照原列表的顺序插入,并且去重
Set<Long> processedIds = new HashSet<>();
for (ResourceCalcSceneSectionResultEig rollResultEig : rollResultEigList) {
Long scprodId = rollResultEig.getScprodId();
// 只添加第一次出现的scprodId,保持原列表顺序
if (!processedIds.contains(scprodId)) {
processedIds.add(scprodId);
// 如果不想覆盖已存在的记录,可以添加这个判断:
// if (!rollEigResults.containsKey(scprodId)) {
rollEigResults.put(scprodId, rollResultEig);
// }
}
}
}
更好的解决方案 - 使用 forEachOrdered 来保持流处理的顺序:
java
public void putRollEigResults(List<ResourceCalcSceneSectionResultEig> rollResultEigList) {
if (rollResultEigList == null || rollResultEigList.isEmpty()) {
return;
}
if (rollEigResults == null) {
rollEigResults = new LinkedHashMap<>();
}
// 使用LinkedHashMap手动保持顺序
Map<Long, ResourceCalcSceneSectionResultEig> tempMap = new LinkedHashMap<>();
rollResultEigList.forEach(rollResultEig -> {
Long scprodId = rollResultEig.getScprodId();
// 如果还没有这个key,就放入(保持第一个出现的顺序)
tempMap.putIfAbsent(scprodId, rollResultEig);
});
// 将处理后的结果放入最终的map
tempMap.forEach(rollEigResults::put);
}
为什么用 putIfAbsent 而不是 merge?
putIfAbsent 会检查key是否存在,不存在才放入,这样就能保持原列表中第一个出现的顺序
这相当于手动实现了 (existing, replacement) -> existing 的逻辑,同时保持了LinkedHashMap的插入顺序
这样修改后, rollEigResults 就会按照原列表 rollResultEigList 中第一次出现的 scprodId 的顺序来排列,保持降序顺序。