前言
今天接到领导通知,然我优化一个导出Excel功能,这个功能在线上4万多条数据大概用了65分钟倒完。
捋顺代码
看一下代码是怎么写的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| public class ExportService{ public Workbook export() {
List<Data> list = getData(); Map<TargetObject, ArrayList<String>> dataMap = new HashMap<ProductColorModel, ArrayList<String>>(); for(Data d : list){ TargetObject obj = new TargetObject(); String mark = d.getMark(); if (dataMap.get(obj) == null){ List<String> markList = new ArrayList(); markList.add(mark); dataMap.put(obj, markList); } else { List<String> markList = dataMap.get(obj); markList.add(mark); dataMap.put(obj, markList); } } for (Map.Entry<TargetObject, ArrayList<String>> entry : dataMap.entrySet()) { TargetObject obj = entry.getKey(); ArrayList<String> markList = entry.getValue(); String marksStr = ""; for (String mark : markList) { marksStr += mark + ";"; } } } }
class TargetObject { private String field1; private String field2; private String field3; private String field4; private String field5; private Date field6; @Override public boolean equals(Object obj) { SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd"); if (obj instanceof TargetObject) { TargetObject model = (TargetObject) obj; if (((model.materialNum == null && materialNum == null) || model.materialNum .equals(materialNum)) && ((model.field1 == null && field1 == null) || model.field1.equals(field1)) && ((model.field2 == null && field2 == null) || model.field2.equals(field2)) && ((model.field3 == null && field3 == null) || model.field2.equals(field3)) && ((model.field4 == null && field4 == null) || model.field2.equals(field4)) && ((model.field5 == null && field5 == null) || model.field2.equals(field5)) && ((model.field6 == null && field6 == null) || ((model.field6 != null && updateDate != null)&&fmt .format(model.field6).equals(fmt.format(field6))))) { return true; } else { return false; } } return super.equals(obj); } @Override public int hashCode() { return 0; } }
|
分析问题
通过阅读前面代码,发现主要问题:TargetObject
的hashCode
方法返回值为0。这个问题在Map的size很小的时候发现不了问题,但是在现在的6W+数据中可以很明显的发现。因为Map底层实现是数组加链表(JDK8之前,本文中项目使用的是JDK7),并且使用Key值的hashcode值作为数组下标进行存储,所以TargetObject
的hashCode
方法返回值固定为0会导致所有的数据都存储在下标为0的链表上,这会导致这个链表很大,即使Map扩容也不会将数据随机分散在数组中。这就导致每一次get都是往全链表遍历的方向走。在export
方法第一个for循环中,dataMap.get(obj)
会越来越慢,到后面基本上就相当于一个N*N的一个循环了,并且get方法在后面的if中还调用了两次,这又乘个2,虽然这是个问题相当于前面的是个小问题,但是也是雪上加霜了。
解决问题
因为这个这里的业务不熟,所以sql就不看了,并且这个sql的速度还可以,在可以接受的范围内,所以我把重点优化的问题放在了Map相关的方向上。
1、将dataMap改为Map<String, TargetObject>
2、删除TargetObject
的hashCode
和equals
3、为TargetObject
添加getString
方法,作为dataMap的Key值
4、为TargetObject
添加markList
字段,和addMark(String)
方法,以及getAllMarkToString
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| public class ExportService{ public Workbook export() {
List<Data> list = getData(); int size = list.size(); Map<String, TargetObject> dataMap = new HashMap<String, TargetObject>(size); List<TargetObject> dataList = new ArrayList<String>(size/2); TargetObject obj; TargetObject hadObj; for(Data d : list){ obj = new TargetObject(); String mark = d.getMark(); String key = obj.getString(); hadObj = dataMap.get(key); if (hadObj == null){ obj.addMark(mark); dataMap.put(key, obj); } else { hadObj.addMark(mark); } } for (TargetObject data : dataList) { String marksStr = data.getAllMarkToString(); } } }
class TargetObject { private String field1; private String field2; private String field3; private String field4; private String field5; private Date field6; private List<String> markList;
public TargetObject(){ this.markList = new ArrayList(); } public void addMark(String mark){ this.markList.add(mark); } public String getAllMarkToString(){ StringBuilder stringBuilder = new StringBuilder(); for (String mark : markList) { stringBuilder.append(mark); } return productsStr.toString(); }
public String getString(){ SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd"); StringBuilder stringBuilder = new StringBuilder(128); stringBuilder.append(field1); stringBuilder.append(field2); stringBuilder.append(field3); stringBuilder.append(field4); stringBuilder.append(field5); if (field6!=null) { stringBuilder.append(fmt.format(field6)); } return stringBuilder.toString(); } }
|
后记
优化后同样数据用了不到一分钟左右搞定,效果还不错。但是我觉得应该还有优化空间,比如sql、两次循环是不是可以一次,这个有时间或者需要再次优化再看吧!
最后推荐大家几款性能定位工具:jstack、jmap、jstat,现在我们单位用的是jstack,可以直接定位卡点,效果非常好~~