1. 项目背景
最近在做一个文件管理系统,类似于百度网盘的功能,可以实现文件及文件夹的上传下载功能,以及权限管理功能,由于文件及文件夹的层级关系,无法进行数据分页操作,否则查询出的数据中原有的层级关系将破坏,无法将具体的文件放到对应的文件夹下,所以在查询出数据量大的情况下,进行文件权限赋值(设置文件的组信息)时效率超低,几百条的数据量处理下来都需要四五秒的时间,领导看了下系统忍不住骂了句:“我去你个大西瓜”。听完后我一愣:什么,你要请我吃西瓜?毕竟系统做成这样,整的我还挺不好意思的。我不停的给领导道歉:对不起对不起。领导看了看我疑惑的眼神,来了句:你知道在对不起之间加哪两个字比较悲催吗?我皱了皱眉头:对三,要不起? 领导用那鄙视的眼神看了我一下,用手指着门外:
好了,言归正传,针对系统响应时间的问题,性能调优无疑成为了系统用户体验的重要指标,然后针对项目的具体情况,我们对代码逻辑做了一点点优化。
2. 原因分析
针对加载时间响应的问题,刚开始我们从数据库和表结构层面做了处理,但是效果不大,后来针对代码逻辑和业务做了分析,主要原因在于给文件赋组权限信息时,由于是循环文件列表,根据文件id去查询对应的组信息,然后拼接后赋值给文件对象属性,虽然单条文件查询组信息时只有0.02-0.03秒的时间,但是数据量大了之后效率还是挺低的。后来我们就想法将所有文件的组信息及文件id全部查出,然后和列表文件的id比较,如果匹配上,则将组信息放到集合中,最后统一赋值给文件对象中的属性。方案想好后,就开始对代码逻辑进行修改实施,最后的测试结果居然提高了20~30倍。具体代码优化如下。
3.代码优化
优化前:
//设置文件对应的组信息 (content是对应的文件集合)
public void setFileGroupInfo(List<?> content){
for(Object object : content){
UserFile userFile = (UserFile) object;
//根据文件id查找组文件信息
List<GroupFile> groupList = groupFileMapper.findGroupFileByFileId(userFile.getId());
Long[] groupIds = new Long[groupList.size()];
for (int i = 0; i < groupList.size(); i++) {
groupIds[i] = groupList.get(i).getGroupId();
}
//给文件赋权,如果文件状态公开或者包含组信息,设为已授权
if(SysConstants.IS_PUBLIC_YES ==userFile.getIsPublic() || groupList.size() > 0){
userFile.setIsAuthorize(1);
}
userFile.setGroupIds(groupIds);
}
}
优化后:
//设置文件对应的组信息 (content是对应的文件集合)
public void setFileGroupInfo(List<?> content){
//由于循环判断content内容然后根据每一个content的id进行查询组信息效率较低,所以可以将content的id取出放到一个集合中,
//然后根据fileIdList使用in一次性查询出所有组信息,然后循环判断content的对象id是否等于组列表的fileId,相等就把对应的组id放到对象的groupIdsList中,
//然后转换为数组赋值给content中的对象,这样效率可以大大提高
List<Long> fileIdList = new ArrayList<>();//fileIdList:存放content中对象id的集合
for(Object object : content){
UserFile userFile = (UserFile) object;
fileIdList.add(userFile.getId());
}
//获取pageResult的content对应的list集合对文件组信息赋值
List<GroupFile> groupList = groupFileMapper.findGroupFileByFileIdList(fileIdList);//groupList:一次性查询出的所有组列表信息
for(Object object : content){
UserFile userFile = (UserFile) object;
//根据文件id查找组文件信息
Long[] groupIds = new Long[]{};//每个文件对应的组信息
List<Long> groupIdsList = new ArrayList<>();//groupIdsList:每个content对象的组id集合,由于循环时数组存放元素不方便,所以此处声明集合,赋值时再转换为对应的数组即可
int groupListSize = groupList.size();
for (int i = 0; i < groupListSize; i++) {
if(userFile.getId().equals(groupList.get(i).getFileId())){//long类型的值不能直接用==比较,要用equals或者userFile.getId().longValue()方法
groupIdsList.add(groupList.get(i).getGroupId());
}
}
//将groupIdsList转换为数组并赋值
groupIds = groupIdsList.toArray(new Long[groupIdsList.size()]);
userFile.setGroupIds(groupIds);
//给文件赋权,如果文件状态公开或者包含组信息,设为已授权
if(SysConstants.IS_PUBLIC_YES ==userFile.getIsPublic() || userFile.getGroupIds().length > 0){
userFile.setIsAuthorize(1);
}
}
}
或许有些网友会问,为什么不改变数据结构,将双层for循环改成map的形式,然后通过遍历map的entryset的方式来提高效率,其实这种构想我们测试过,性能变化不明显。不过此处的for循环代码可以通过jdk8的lambda表达式和流特性进行简化,此处就不再一一赘述。
4. 结尾
一周就这样快要过去了,明天还得坚持最后一天上班模式。关于上班,其实就好比旧时代的婚姻,明明不幸福,还得长相厮守。都说生活中百分之二十的痛苦来自于上班,百分之八十的痛苦来自于没钱,所以上班和没钱之间,我们不得不选择前者。