Java8的字符串list分组、排序和SQL的to_char函数使⽤⼀、背景
本篇出⾃最近笔者所负责项⽬的⼀个需求。开发之余,就此次功能实现过程,结合最近所学所感,笔者做了些记录,留下此篇。
这是笔者此次需要实现的需求效果图:
⾸先就是这个当前页的时间轴数据展⽰,于前端讲就是个数组,于后端讲就是个list数据结构。
本次项⽬,前端采⽤Vue,后端则为spring boot+Mybatis。
⼆、数据格式
经过和前端反复扯⽪,基本确定了返回数据格式(当然,这是笔者胡说的,事实上此后还经过两次变动)。格式如下:
data: [{
year: '2020 年',
area: [{ data: '06 - 20', num: 9, fileList: { fileId: 12, fullName: '农政改发[2020] 6 号', size: '200 kb' }, arealist: ['安徽省'] }]groupby是什么函数
}]
三、表设计和字段关联
笔者在原有基础上新增了⼀张表,字段如下:
以此表为主表,去关联另⼀张表,关联字段为area_id。
后⾯要⽤到的字段其实就是省份名称,试验区名称和批复⽇期。
四、业务逻辑和实现过程
开发是什么?⼤公司的是怎么样的,笔者不太清楚。但我本⼈的感受就是,开局⼀张图(PPT),其
余全靠编。让笔者感慨的是,从⼩时候上学开始,⼀路⾛来,经历了多少看图写作⽂,命题作为,半命题作⽂。没想到,⼯作后也是如此。
之前提到需要list结构返回数据,但是list内部的数据结构如何组织并没有确定。仔细分析以后发现这就是⼀个套娃结构,⾥⾥外外套了5层。如果是单表的话还好说,可这是需要跨表关联查询。按照前端的要求,要按照年份分组;⽽分析需求,结合表结构和字段关联,内部还需要按照批复⽂件id即file_id分组;⽽后来前端⼜补充,同⼀省份的不同试验区也应该归为⼀组。总结⼀下,就是⾄少需要三次分组。年份
字段需要从表字段approve_date截取,还有内层的⽇期字段包含⽉⽇。
谈到分组和字段的拼接,其实笔者最初是倾向于尽量⽤SQL实现,那样的话代码层就简简单单,⼏⾏了事。当然,这也是笔者的思维惯性:能SQL解决就尽量不写代码,还是怕⿇烦,尤其是对脏数据和空值处理。
可惜没能如我所愿。SQL实现的话,有些复杂,⽽且项⽬负责⼈和同事也不建议那么做。那就上代码,如图:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-////DTD Mapper 3.0//EN" "/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="form.mapper.ApproveFileMapper">
<!-- 查询获取批复⽂件信息列表 -->
<select id="getAllApproveFile" resultType="sponse.developmentprocess.ApproveFileResVo">
SELECT to_char(af.approve_date, 'yyyy年') AS year, to_char(af.approve_date, 'mm-dd') AS approve_date,
af.file_id, f.full_name, f.size, rpa.province_region_name AS province_name, rpa.name AS area_name
FROM approve_file af LEFT JOIN reform_pilot_area rpa ON af.area_id = rpa.id
LEFT JOIN file f ON af.file_id = f.id WHERE af.id IS NOT NULL ORDER BY to_char(af.approve_date, 'yyyy') DESC
</select>
</mapper>
项⽬采⽤的是PG数据库,在Mybatis中采⽤to_char字段处理localDate类型数据,将其转换为字符串类型数据时完全可以做到的。有关
to_char这个函数的应⽤相关技巧,⽹上资料多的是,笔者也不再赘述。这⾥要提⼀点最后order by函数排序,其实就最终要返回的数据⽽⾔,并没有什么作⽤。因为经过后续多次分组、遍历以后,最终拼接得到的数据早已⽆法按照原始数据那样排序。
关键代码,如图:
@Override
public List<DevProcessResVo> getDataList() {
List<ApproveFileResVo> approveFileResVoList = getAllApproveFile();
// 对数据源列表进⾏测试,如果判断为空,返回⼀个新建ArrayList列表
if (approveFileResVoList == null || approveFileResVoList.size() == 0)
return new ArrayList<>();
// 根据年份和批复⽂件id对批复⽂件信息列表分组
Map<String, Map<Long, List<ApproveFileResVo>>> approveFileMap = approveFileResVoList.stream().collect(Collectors
.groupingBy(ApproveFileResVo::getYear, upingBy(ApproveFileResVo::getFileId)));
List<DevProcessResVo> devProcessResVoList = new ArrayList<>();
// 遍历approveFileMap,拼接出所需要的数据结构
// ⼀次遍历,以批复⽂件年份为key,对应值为同⼀年份下的所有数据
approveFileMap.forEach((year, map) -> {
DevProcessResVo devProcessResVo = new DevProcessResVo();
devProcessResVo.setYear(year);
List<AreaResVo> areaResVoList = new ArrayList<>();
// ⼆次遍历,以批复⽂件id为key,对应值为同年份同批复⽂件id下的所有数据
map.forEach((key2, value) -> {
AreaResVo areaResVo = new AreaResVo();
areaResVo.setFileId(key2);
List<SimplePilotAreaResVo> simplePilotAreaResVos = new ArrayList<>();
value.forEach(v -> {
areaResVo.ApproveDate());
areaResVo.FullName());
areaResVo.Size());
SimplePilotAreaResVo simplePilotAreaResVo = new SimplePilotAreaResVo();
simplePilotAreaResVo.ProvinceName());
simplePilotAreaResVo.AreaName());
simplePilotAreaResVos.add(simplePilotAreaResVo);
});
areaResVo.setNum(simplePilotAreaResVos.size());
List<AreaGroupByProvinceResVo> group = new ArrayList<>();
// 根据省份名称将试验区列表分组
Map<String, List<SimplePilotAreaResVo>> areaMap = simplePilotAreaResVos.stream().upingBy(SimplePilotAreaResVo::getProvin areaMap.forEach((province, list) -> {
AreaGroupByProvinceResVo areaGroupByProvinceResVo = new AreaGroupByProvinceResVo();
areaGroupByProvinceResVo.setProvinceName(province);
List<String> areaNameList = new ArrayList<>();
list.forEach(t -> {
String name = t.getAreaName();
areaNameList.add(name);
});
areaGroupByProvinceResVo.setAreaNameList(areaNameList);
group.add(areaGroupByProvinceResVo);
});
areaResVo.setAreaList(group);
areaResVoList.add(areaResVo);
});
devProcessResVo.setArea(areaResVoList);
devProcessResVoList.add(devProcessResVo);
});
// 将拼接后获取的发展历程数据列表进⾏降序排序
Collections.sort(devProcessResVoList, Comparatorparing(DevProcessResVo::getYear).reversed());
return devProcessResVoList;
}
在Java8的stream相关API中,对于list的处理⽅法很丰富,本次⽤到的单条件分组和多条件分组只是其中之⼀。需要指出的是,Stream流分组处理数据时遇到null值会抛异常并中断程序。所以,笔者在拿到原始数据后进⾏测试,判空后直接返回新建ArrayList。提到空值和空指针异常,我们都知道Java中这是最常见的情况。最近笔者查看了⼀部分Jdk1.8的源码,发现源码中很重视边界检查和测试。笔者是⼗分赞同这样的做法的。笔者认为,任何从数据库中拿到的原始数据,哪怕是⾃⼰本⾝⼗分确定数据的可靠性,但还是应该对原始数据做测试,加⼀道保险。
前⾯已经提到过,经过拼接后得到的devProcessResVoList不再是降序排序的,所以要经过⼀层排序处理然后才能得到所需要的数据。这⾥排序,笔者⽤了Collections.sort()。当然有其他的实现⽅式,但此⽅法笔者⽐较熟悉。这⾥引发了本⼈的⼀个思考,那就是能得到正确结果的排序⽅式有⼏种?各⾃的效率如何?哪个最优?哪个最适合⼤数据量的处理?由于时间问题,笔者只是简单的尝试了⼀下stream的sort⽅法,似乎不太理想。stream的sort⽅法直接提供了int、double、Long类型版本的Comparatorparaing(),并没有string类型的直接⽅法,⽽并⾏流似乎也并没有提供。也许是限于笔者的stream相关技能的⽔平吧,并没有很快的做出对⽐。出于时间和代码简洁度的考虑,最后选择了Collections.sort()。
最后再提⼀点,就是关于ArrayList的采⽤。之前笔者测试过,不过测试数据是整型。在相同且相当⼤的数据量情况下,ArrayList的add⽅法效果要⽐LinkedList实际要差点,但是都在⼀个量级。其实理论上两者的add()⽅法都是默认加到最后⼀项,应该为O(N)。但如果是涉及到get⽅法和set⽅法,其差距就很明显。LinkedList所花时间是随着数据量增长⽽成平⽅增长的。即使是add(index, int),虽然LinkedList所需时间⽐ArrayList的少,⼤约差⼀到两个数量级,但是综合考虑,只要有get的调⽤,还是⽐较推荐ArrayList。当然,过⼤的数据集不在此考虑范围内。
五、总结
本篇并没有⾼⼤上的技能分享,也没有真知灼见。是笔者结合⾃⼰的项⽬经历,和最近的学习吸收,写了⼀些总结。关于最后的字符串list 排序问题,如果⼤家有更⾼效,更优雅的代码实现,还请不吝赐教。如果能出⼀篇各⽅法对⽐和⼤数据量测试⽂章,那就更好了。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论