# 七行代码的九种性能优化方法 原题如下,如下代码用于拼接字符串,试找出所有9种性能优化方法 ```java public String buildProvince(List<Org> orgs){ StringBuilder sb = new StringBuilder(); for(Org org:orgs){ if(sb.length()!=0){ sb.append(",") } sb.append(org.getProvinceId()); } return sb.toString(); } ``` > 所有优化方法实现在 https://gitee.com/xiandafu/java-performance/tree/master/helloworld,优化的JMH测试结果也在例子的comment里 > > 这个例子的9种优化方法,在本书里均有介绍 ## 1 检查是否为空 提前检测参数,尽早返回可能的结果. ```java public static String buildProvinceWithEmptyCheck(List<Org> orgs){ //检测是否为空 if(orgs.isEmpty()){ return ""; } ....... } ``` ## 2 检测集合只有一个 如果集合只有一个,则只需要做特殊处理 ```java public static String buildProvinceWithSizeCheck(List<Org> orgs){ //如果只包含一个元素 if(orgs.size()==1){ return String.valueOf(orgs.get(0).getProvinceId()); } ....... } ``` Java的String的split方法,就是对正则表达式判断,如果长度为1,则不采用正则表达式解析字符串,从而提供系统性能 ## 3 采用subString ```java public static String buildProvinceWithSubString(List<Org> orgs){ StringBuilder sb = new StringBuilder(); for(Org org:orgs){ sb.append(org.getProvinceId()).append(","); } //去掉最后一个逗号 return sb.substring(0,sb.length()); } ``` 这样,避免了每次循环中的使用if来判断。(注意,这里假定orgs是有值的) ## 4 使用char拼接 ```java public static String buildProvinceWithChar(List<Org> orgs){ StringBuilder sb = new StringBuilder(); for(Org org:orgs){ //使用char 代替String sb.append(org.getProvinceId()).append(','); } sb.setLength(sb.length()-1); return sb.toString(); } ``` 通过StringBuilder源码,可以看到append(char)效率比append(String)要高多了 ## 5 预先估计StringBuilder容量 对集合,StringBuilder等预先估计一个容量,避免容量再次增长带来的性能影响 ```java public static String buildProvinceWithInitCapacity(List<Org> orgs){ //假设省Id不超过2位,初始化一个大小 StringBuilder sb = new StringBuilder(orgs.size()*3); ....... } ``` ## 6 采用StringBuffer StringBuffer并不比StringBuilder慢 ```java public static String buildProvinceWithStringBuffer(List<Org> orgs){ //StringBuffer StringBuffer sb = new StringBuffer(); ........ return sb.toString(); } ``` 一方面虚拟机有锁逃逸优化,避免了Synchronized 带来的性能影响,另外,StringBuffer的toString方法实现比较特别,它的实现如下 ```java @Override public synchronized String toString() { return new String(toStringCache, true); } ``` 也就是用StringBuffer构造String的时候,复用了StringBuffer的char[], 对比StringBuilder,则采用更安全的方式,构造String的时候重新复制了一份。 ## 7 provinceId提前转为字符串 int转为字符串实际上相当耗时,可以提前转化,这也是所有优化方式里最明显的的一种 ```java public static String buildProvinceWithCacheData(List<Org> orgs){ StringBuilder sb = new StringBuilder(); for(Org org:orgs){ //使用预先初始化好的chars char[] chars = strProvince[org.getProvinceId()]; sb.append(chars).append(','); } sb.setLength(sb.length()-1); return sb.toString(); } static char[][] strProvince; static{ strProvince = new char[31][]; for(int i=0;i<31;i++){ strProvince[i] = String.valueOf(i).toCharArray(); } } ``` 再Java源码里,int装箱成Integer,也采用预先初始化了一部分Integer数据,以提高性能 ## 8 缓存StringBuilder 如果每次调用都需要构建一个StringBuilder,可以放到ThreadLocal里缓存起来,也有较好的性能 ```java public static String buildProvinceWithCache(List<Org> orgs){ StringBuilder sb = local.get(); for(Org org:orgs){ if(sb.length()!=0){ sb.append(","); } sb.append(org.getProvinceId()); } String str = sb.toString(); sb.setLength(0); return str; } static ThreadLocal<StringBuilder> local = ThreadLocal.withInitial((Supplier) () -> new StringBuilder()); ``` 在Java源码里,FloatingDecimal,用于讲Double转化String,也是采用了类似机制。 Jackson,FastJSON等高速序列化工具,也会提供可复用的输出缓存区。如Jackson使用了BufferRecycler ## 9 循环展开 其他语言都有循环展开作为性能优化,Java也有此效果,这里仅仅作为演示例子,在此场景不具备实际用途 ```java /** * 如果预先知道容量,比如能整除4,可以减少循环判断 * @param orgs * @return */ public static String buildProvinceWithLoop(List<Org> orgs){ StringBuilder sb = new StringBuilder(); int count = orgs.size()/4; int i=0; for(;i<count;i++){ sb.append(orgs.get(i).getProvinceId()).append(","); sb.append(orgs.get(i+1).getProvinceId()).append(","); sb.append(orgs.get(i+2).getProvinceId()).append(","); sb.append(orgs.get(i+3).getProvinceId()).append(","); } //去掉最后一个逗号 sb.setLength(sb.length()-1); return sb.toString(); } ```