本文实现了比较两个List之间的差异,包括获取两List的差集,交集,并集(不去重)和 并集(去重)的API解法和优化解法的解决方案,并根据实际业务场景实现业务需求。
本文中在比较两个List之间的差异时,为了代码简洁未对入参进行空值判断,根据实际需求可以对入参进行非空判断以及相应的业务处理。
求差集
/**
* 差集(基于API解法) 适用于小数据量
* 求List1中有的但是List2中没有的元素
* 时间复杂度 O(list1.size() * list2.size())
*/
public static List<String> subList(List<String> list1, List<String> list2) {
list1.removeAll(list2);
return list1;
}
/**
* 差集(基于常规解法)优化解法1 适用于中等数据量
* 求List1中有的但是List2中没有的元素
* 空间换时间降低时间复杂度
* 时间复杂度O(Max(list1.size(),list2.size()))
*/
public static List<String> subList1(List<String> list1, List<String> list2) {
//空间换时间 降低时间复杂度
Map<String, String> tempMap = new HashMap<>();
for(String str:list2){
tempMap.put(str,str);
}
//LinkedList 频繁添加删除 也可以ArrayList容量初始化为List1.size(),防止数据量过大时频繁扩容以及数组复制
List<String> resList = new LinkedList<>();
for(String str:list1){
if(!tempMap.containsKey(str)){
resList.add(str);
}
}
return resList;
}
/**
* 差集(基于java8新特性)优化解法2 适用于大数据量
* 求List1中有的但是List2中没有的元素
*/
public static List<String> subList2(List<String> list1, List<String> list2) {
Map<String, String> tempMap = list2.parallelStream().collect(Collectors.toMap(Function.identity(), Function.identity(), (oldData, newData) -> newData));
return list1.parallelStream().filter(str->{
return !tempMap.containsKey(str);
}).collect(Collectors.toList());
}
求交集
/**
* 交集(基于API解法) 适用于小数据量
* 求List1和List2中都有的元素
* 时间复杂度 O(list1.size() * list2.size())
*/
public static List<String> intersectList(List<String> list1, List<String> list2){
list1.retainAll(list2);
return list1;
}
/**
* 交集(基于常规解法) 优化解法1 适用于中等数据量
* 求List1和List2中都有的元素
* 时间复杂度O(Max(list1.size(),list2.size()))
*/
public static List<String> intersectList1(List<String> list1, List<String> list2){
//空间换时间 降低时间复杂度
Map<String, String> tempMap = new HashMap<>();
for(String str:list2){
tempMap.put(str,str);
}
//LinkedList 频繁添加删除 也可以ArrayList容量初始化为List1.size(),防止数据量过大时频繁扩容以及数组复制
List<String> resList = new LinkedList<>();
for(String str:list1){
if(tempMap.containsKey(str)){
resList.add(str);
}
}
return resList;
}
/**
* 交集(基于java8新特性)优化解法2 适用于大数据量
* 求List1和List2中都有的元素
*/
public static List<String> intersectList2(List<String> list1, List<String> list2){
Map<String, String> tempMap = list2.parallelStream().collect(Collectors.toMap(Function.identity(), Function.identity(), (oldData, newData) -> newData));
return list1.parallelStream().filter(str->{
return tempMap.containsKey(str);
}).collect(Collectors.toList());
}
求并集(不去重)
/**
* 并集(不去重)
* 合并list1和list2 不考虑去除重复元素
* 数组扩容 数组copy
* @param list1
* @param list2
* @return
*/
public static List<String> mergeList(List<String> list1, List<String> list2){
list1.addAll(list2);
return list1;
}
求并集(除去重复)
/**
* 并集(去重) 基于API解法
* 合并list1和list2 去除重复元素
* 时间复杂度主要取决于removeAll 取差集 O(list1.size() * list2.size())
*/
public static List<String> distinctMergeList(List<String> list1, List<String> list2){
//第一步 先求出list1与list2的差集
list1.removeAll(list2);
//第二部 再合并list1和list2
list1.addAll(list2);
return list1;
}
/**
* 并集(去重) 基于Java8新特性 适用于大数据量
* 合并list1和list2 去除重复元素
*/
public static List<String> distinctMergeList1(List<String> list1, List<String> list2){
//第一步 先求出list1与list2的差集
list1 = subList2(list1,list2);
//第二部 再合并list1和list2
list1.addAll(list2);
return list1;
}
实际业务场景
根据客户需求,业务提交审核需要很直观的看到此次提交的数据关联产品的状态变更。
第一种情况:新增的渠道授权关联的产品,所有的授权产品均为新增;
第二种情况:已审核通过的渠道授权重新提交授权审核的,要直观的标记出此次提交审核渠道关联授权产品新增了那些,删除了那些,更改了那些等信息;
第三种情况:作废渠道提交的审核要标注出所有的关联授权产品为删除状态。
授权关联产品为申请表单中一对多关联表,前端展示根据数据的不同状态展示不同的样式:
新增授权产品显示为红色
删除授权产品显示为删除线样式(中划线 )
更新授权产品显示标注红色*号
建立关联产品Vo
首先模拟建立一个产品的实体,此处只简单列入几个属性,在比较所关联产品信息是否是变更状态的时候根据实际业务需要需重写 hashCode 和 equals 方法。
class ProductVo{
private String id;
private String name;
//其他属性不在列入
//数据状态(新增:insert; 更新:update; 删除:delete)
private String status;
//get set 省略
//如有必要重写hashCode equals
}
业务代码实现
业务实现主要通过 空间换时间 方式降低时间复杂度,先把List转为Map,利用map的 get 和 containsKey 方法理想情况下O(1)的时间复杂度降低嵌套的两次List遍历。
/**
* 渠道授权新提交关联授权产品 与 历史已审批授权信息对比处理标注授权产品的状态信息<br/>
* 前端可以根据不同的数据状态显示不同的样式<br/>
* 用于审核人员直接看到此次提交审核新增了那些授权,取消了那些授权,更改了那些授权
* @param oldList 原始关联授权产品列表
* @param newList 提交关联授权产品列表
* @return
*/
public List<ProductVo> productStatusHandle(List<ProductVo> oldList,List<ProductVo> newList){
//原始关联授权产品为空 并且 新关联授权产品为空(基本不存在此场景)
if((oldList == null || oldList.isEmpty()) && (newList == null || newList.isEmpty())){
return Collections.emptyList();
}
//原始关联授权产品为空 则提交关联授权产品全部为新增
if(oldList == null || oldList.isEmpty()){
return newList.stream().map(vo->{
vo.setStatus("insert");
return vo;
}).collect(Collectors.toList());
}
//提交关联授权产品为空 则删除之前所有的产品授权
if(newList == null || newList.isEmpty()){
return oldList.stream().map(vo->{
vo.setStatus("delete");
return vo;
}).collect(Collectors.toList());
}
//原始关联授权产品与此次提交关联授权产品均不为空
List<ProductVo> resList = new LinkedList<>();
//空间换时间 降低时间复杂度
//说明:list中不会存在重复(ID相同)的授权产品 否则此toMap收集会抛出异常
Map<String, ProductVo> oldMap = oldList.stream().collect(Collectors.toMap(ProductVo::getId, Function.identity()));
Map<String, ProductVo> newMap = newList.stream().collect(Collectors.toMap(ProductVo::getId, Function.identity()));
for(ProductVo vo:newList){
ProductVo productVo = oldMap.get(vo.getId());
//提交关联授权产品在原始关联授权产品
if(productVo != null){
if(!vo.equals(productVo)){//重写hashCode与equals自定义规则 用于判定是否数据更新
vo.setStatus("update");
}
}else{//提交审核数据不在旧数据之列
vo.setStatus("insert");
}
resList.add(vo);
}
//原始关联授权产品是否存在已取消的情况
for(ProductVo vo:oldList){
if(!newMap.containsKey(vo.getId())){
vo.setStatus("delete");
resList.add(vo);
}
}
return resList;
}