如何实现List<Map<String,Object>>类型双数据集对账匹配?
双List<Map<String,Object>>数据集对账匹配实现
需求说明
对两个List<Map<String,Object>>类型数据集(左侧LHS=query1results、右侧RHS=query2results)执行对账匹配,需统计匹配数与断项数,规则如下:
- 同时存在于LHS和RHS的记录为匹配项,需保留双方的匹配Map;
- 仅存在于LHS的记录为右断项(right-hand-breaks);
- 前两步处理后未匹配的RHS记录为左断项(left-hand-breaks);
- 匹配判断仅基于指定key的取值,忽略其他key。
现有尝试代码
尝试1:仅实现LHS匹配,未处理断项
@Override public void reconcile(LocalDate date) { List<Map<String, Object>> query1Records = executeQuery1(date).collect(Collectors.toList()); List<Map<String, Object>> query2Records = executeQuery2(date).collect(Collectors.toList()); List<Map<String, Object>> matching = query1Records.parallelStream().filter(searchData -> query2Records.parallelStream().anyMatch(inputMap -> searchData.get("instrument").equals(inputMap.get("instrument")) && String.valueOf(searchData.get("entity")).equals(inputMap.get("entity")) && searchData.get("party").equals(inputMap.get("party")) && ((BigDecimal) searchData.get("quantity")).compareTo((BigDecimal) inputMap.get("quantity")) == 0)) .collect(Collectors.toList()); }
尝试2:按指定key匹配,但未保留双方匹配记录、未处理RHS断项
List<String> keys = Arrays.asList("entity", "instrument", "party", "quantity"); Function<Map<String, Object>, List<?>> getKey = m -> keys.stream().map(m::get).collect(Collectors.toList()); Map<List<?>, Map<String, Object>> bpsKeys = query1Records.stream() .collect(Collectors.toMap( getKey, m -> m, (a, b) -> { throw new IllegalStateException("duplicate " + a + " and " + b); }, LinkedHashMap::new)); List<Map<String,Object>> matchingRecords = query2Records.stream() .filter(m -> bpsKeys.containsKey(getKey.apply(m))) .collect(Collectors.toList()); matchingRecords.forEach(m -> bpsKeys.remove(getKey.apply(m))); List<Map<String,Object>> notMatchingRecords = new ArrayList<>(bpsKeys.values());
完整解决方案
实现思路
通过指定key生成唯一匹配键,将LHS和RHS分别按匹配键构建映射表,快速查找匹配项;遍历映射表完成匹配项收集、断项分类,完整覆盖所有对账规则。
代码实现
import java.math.BigDecimal; import java.time.LocalDate; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; // 对账结果容器类,用于存储匹配项和断项,方便统计 class ReconciliationResult { private List<Map<String, Object>> matchingLhsRecords; private List<Map<String, Object>> matchingRhsRecords; private List<Map<String, Object>> rightHandBreaks; // LHS独有记录 private List<Map<String, Object>> leftHandBreaks; // RHS独有记录 public ReconciliationResult() { this.matchingLhsRecords = new ArrayList<>(); this.matchingRhsRecords = new ArrayList<>(); this.rightHandBreaks = new ArrayList<>(); this.leftHandBreaks = new ArrayList<>(); } // 按需添加getter方法 public List<Map<String, Object>> getMatchingLhsRecords() { return matchingLhsRecords; } public List<Map<String, Object>> getMatchingRhsRecords() { return matchingRhsRecords; } public List<Map<String, Object>> getRightHandBreaks() { return rightHandBreaks; } public List<Map<String, Object>> getLeftHandBreaks() { return leftHandBreaks; } // 打印对账统计信息 public void printReconciliationStats() { System.out.println("匹配记录数:" + matchingLhsRecords.size()); System.out.println("右断项数(仅LHS存在):" + rightHandBreaks.size()); System.out.println("左断项数(仅RHS存在):" + leftHandBreaks.size()); } } @Override public ReconciliationResult reconcile(LocalDate date) { List<Map<String, Object>> lhsRecords = executeQuery1(date).collect(Collectors.toList()); List<Map<String, Object>> rhsRecords = executeQuery2(date).collect(Collectors.toList()); // 定义用于匹配的key集合,忽略其他key List<String> matchKeys = Arrays.asList("entity", "instrument", "party", "quantity"); // 生成匹配键的函数:统一处理key对应的取值格式,避免类型差异导致匹配失败 Function<Map<String, Object>, List<Object>> generateMatchKey = map -> matchKeys.stream() .map(key -> { Object value = map.get(key); // 统一entity为String类型,避免不同类型的相等判断问题 if ("entity".equals(key)) { return String.valueOf(value); } // quantity为BigDecimal,直接使用(已重写equals和hashCode) return value; }) .collect(Collectors.toList()); // 将LHS记录按匹配键映射,若存在重复匹配键则抛出异常(可根据业务调整) Map<List<Object>, Map<String, Object>> lhsKeyMap = lhsRecords.stream() .collect(Collectors.toMap( generateMatchKey, record -> record, (existing, newRecord) -> { throw new IllegalStateException("LHS存在重复匹配记录:" + existing); }, LinkedHashMap::new )); // 将RHS记录按匹配键映射 Map<List<Object>, Map<String, Object>> rhsKeyMap = rhsRecords.stream() .collect(Collectors.toMap( generateMatchKey, record -> record, (existing, newRecord) -> { throw new IllegalStateException("RHS存在重复匹配记录:" + existing); }, LinkedHashMap::new )); ReconciliationResult result = new ReconciliationResult(); // 处理匹配项和右断项 for (Map.Entry<List<Object>, Map<String, Object>> lhsEntry : lhsKeyMap.entrySet()) { List<Object> matchKey = lhsEntry.getKey(); Map<String, Object> lhsRecord = lhsEntry.getValue(); if (rhsKeyMap.containsKey(matchKey)) { // 记录匹配项,同时从RHS映射中移除已匹配的键 result.getMatchingLhsRecords().add(lhsRecord); result.getMatchingRhsRecords().add(rhsKeyMap.get(matchKey)); rhsKeyMap.remove(matchKey); } else { // LHS独有,归入右断项 result.getRightHandBreaks().add(lhsRecord); } } // 剩余的RHS记录全部为左断项 result.getLeftHandBreaks().addAll(rhsKeyMap.values()); // 输出统计结果 result.printReconciliationStats(); return result; }
方案亮点
- 高效匹配:利用Map的O(1)查找特性,避免嵌套Stream的O(n²)时间复杂度;
- 规则全覆盖:完整收集双方匹配记录、LHS独有的右断项、RHS独有的左断项;
- 格式兼容:统一处理key对应的取值格式(如entity转String),避免因类型差异导致的匹配错误;
- 异常处理:检测并抛出重复匹配键的异常,避免对账结果混乱(可根据业务需求调整为合并或跳过重复记录)。
内容的提问来源于stack exchange,提问作者Beez
相关产品推荐
相关产品推荐

