JAVA 容器集合+数据结构 模拟SQL实现交并差统计等操作
JAVA代码同样可以通过合理运用数据结构和算法的特点实现SQL中的大部分操作,例如:JOIN ON ,WHERE ,UNION ,IN/NOT IN ,GROUPBY ,TOP ,LIMIT 等等
大数据量的 去重、交并差.....等集合操作,都可以适当借助中间容器实现,并且合理运用不同的数据结构的特点做中间容器,可以大大提高效率优化时间复杂度。尽可能避免循环嵌套的遍历,造成倍数级别(n行 x m行)的时间复杂度。注意根据需要重写hashcode()和equals()。还要考虑中间容器构造的时间和空间消耗
JDK提供了一些原生方法:retainAll,addAll,removeAll,但是有些效率一般
数据结构的特点:Hash、Tree结构适合查找频繁的场景,Array结构适合随记、下标访问,Set可以去重等等,堆做TOP。
List<List/Set/Map/Object>,Map<key,List/Set/Map/Object> 将相同特性的放在一个List中,实现一KEY多值、分组等。方便查找过滤分组。以及实现类似矩阵等更复杂数据结构
在合适的场景也可以加入多线程处理提高速度。
例如:
以下只是个人意见,有其他更好思路希望大家留言
求两个List的交集/差集/查重: 其核心是查找,可以借助HashSet/Map将大List放入其中作为查询字典,另一个List去HashSet中查找是否存在。此思路借助HASH表结构完成查找,避免循环嵌套的遍历,提高了对比效率。
求两个List的完全差集:可以借助Map,将大List作为K放入其中,V记录出现次数。另一个List去Map中查找,存在则更新V=2,不存在则作为K放入其中。最后只需要找出Map中V为1的K。
给List<User>按照年龄分组:遍历List<User> 保存到HashMap<age,List<User>>中。分组后的值为HashMap.values()=List<List<User>>。JDK8可直接使用Stream分组
用Java代码替换Sql语句:有时考虑到数据库压力和并发问题,只能执行简单的Sql语句返回基础表数据。这时需要在Java的Service层中处理这些原始数据List,代替SQL中的操作。
Distinct:去重。将List保存到Set中实现去重。
Sum:统计出现次数。可以借助HashMap<T,Integer> 结构,遍历List<T>去查找HashMap中更新Integer++。
In/Not in: 求交集差集,其核心是查找,可以借助HashSet/Map将大List放入其中作为查询字典,另一个List去HashSet中查找是否存在。此思路借助HASH表结构完成查找,避免循环嵌套的遍历,提高了对比效率。
Where:相当于过滤集合。推荐Guava库提供的过滤方法。
Groupby:分组。JDK8可直接使用Stream分组,或通过Map<分组id,List>结构保存。
Join:关联(一对一,一对多),可以将作为查询字典的集合放入HashMap中,key作用Join On关联条件,查找并更信息相应属性值/引用。如果是多条件关联,可以考虑条件合并,或是map嵌套Map<on条件1,Map<on条件2,表Object>>
class 主表Object{
子表Object obj; //一对一
List<子表Object> list; //一对多
}
一对多方案:
方案1:HashMap字典主表List。将List<主表Object>放入HashMap<on条件,主表Object>中。遍历List<子表Object>,通过on条件从HashMap中找到主表Object,并放入其List<子表Object>中。
经测试:主表4万,子表8万数据量,子表无索引。 使用java与原生sql执行join对比,记录从查询到完成时间差。java速度没有不慢于sql。
方案2:HashMap分组子表List。与方案1思路相似,只是改成Map分组子表,将List<子表Object>分组为Map<on条件,List<子表Object>>结构,遍历List<主表Object>去分组后的Map中找对应List。
方案3:过滤子表List。遍历List<主表Object>,按on条件使用Guava库方法过滤List<子表Object>,放入主表Object中。
方案4:同步下标遍历。假设工号为关联条件,ArrayList<主表Object>和List<子表Object> 都按照工号排序,用一个int index=0记录ArrayList<主表Object>当前下标。遍历List<子表Object>,把子表Object放入ArrayList<主表Object>.get(index)中。当判断 子表Object.工号<>ArrayList<主表Object>.get(index).工号 时下移主表下标 index++。这样只需要遍历List<子表Object>和ArrayList<主表Object>都只遍历一次。前提是主/子都按照相同条件顺序排序。
//方案4 伪代码
//一对多场景下,主表为小表
List<主表Object> list1; //按照id排序
List<子表Object> list2; //按照id排序
主表Object o1;
子表Object o2;
int sub_index=0;
Iterator iter1 = list1.iterator();
//外循环小表
while(iter1.hasNext()){
o1 = (主表Object) iter1.next();
while(sub_index<list2.size()){
o2=list2.get(sub_index); //取得子表当前下标位置元素
if (o2.getid().equals(o1.getid())){ //判断关联条件
o1.getSubList.add(o2);
sub_index++; //更新子表当前下标
}else{
break;
}
}
}
频繁需要查询的集合最好使用Hash、Tree结构保存
Top K 问题
其本质上是排序问题,常用思路:快速排序(求小 左递归,求大 右递归)、堆排序(适合大数据量) .。
堆排序虽然本身速度没有快排和归并这种分治排序快,但是当统计外部大数据量时,求TOP K只需要建立容量为K的堆,非常节省内存开销。
JAVA中PriorityQueue 优先级队列是小根堆结构,但是可以通过comparator自定义大小,转换成大根堆模式。
PriorityQueue(int initialCapacity,Comparator<? super E> comparator)
集合第三方JAR推荐
google.Guava , Apache Commons Collections 包含很多高效的集合操作API和新类型的数据结构。
推荐Guava,经测试平均执行效率高于JDKAPI(Lists,Sets,Maps,Collections2等工具类中提供了包括集合交并差过滤等多种高效操作)。而且除了集合,还有很多高效简单的工具类,涉及String,Object,I/O等等
https://blog.csdn.net/dgeek/article/details/76221746