字符串|昨天还在 for 循环里写加号拼接字符串的那个同事,今天已经不在了
来源:
测试用例
测试结果
结果分析
第一组
第二组
扩展
结论
引言
都说 StringBuilder 在处理字符串拼接上效率要强于 String , 但有时候我们的理解可能会存在一定的偏差 。 最近我在测试数据导入效率的时候就发现我以前对 StringBuilder 的部分理解是错误的 。 后来我通过实践测试 + 找原理 的方式搞清楚了这块的逻辑 。 现在将过程分享给大家
测试用例
我们的代码在循环中拼接字符串一般有两种情况
第一种就是每次循环将对象中的几个字段拼接成一个新字段 , 再赋值给对象
第二种操作是在循环外创建一个字符串对象 , 每次循环向该字符串拼接新的内容 。 循环结束后得到拼接好的字符串
对于这两种情况 , 我创建了两个对照组
第一组:
在每次 For 循环中拼接字符串 , 即拼即用、用完即毁 。 分别使用 String 和 StringBuilder 拼接
/** * 循环内 String 拼接字符串 , 一次循环后销毁 */ public static void useString(){ for (int i = 0 i < CYCLE_NUM_BIGGER i++) { String str = str1 + i + str2 + i + str3 + i + str4} }
/** * 循环内 使用 StringBuilder 拼接字符串 , 一次循环后销毁 */ public static void useStringBuilder(){ for (int i = 0 i < CYCLE_NUM_BIGGER i++) { StringBuilder sb = new StringBuilder() String s = sb.append(str1).append(i).append(str2).append(i).append(str3).append(i).append(str4).toString() } }
第二组:
多次 For 循环拼接一个字符串 , 循环结束后使用字符串 , 使用后由垃圾回收器回收 。 也是分别使用 String 和 StringBuilder 拼接
/** * 多次循环拼接成一个字符串 用 String */ public static void useStringSpliceOneStr (){ String str = "" for (int i = 0 i < CYCLE_NUM_LOWER i++) { str += str1 + str2 + str3 + str4 + i } }
/** * 多次循环拼接成一个字符串 用 StringBuilder */ public static void useStringBuilderSpliceOneStr(){ StringBuilder sb = new StringBuilder() for (int i = 0 i < CYCLE_NUM_LOWER i++) { sb.append(str1).append(str2).append(str3).append(str4).append(i) } }
为了保证测试质量 , 在每个测试项目进行前 。 线程休息 2s , 之后空跑 5 次热身 。 最后执行 5 次求平均时间的方式计算时间
public static int executeSometime(int kind, int num) throws InterruptedException { Thread.sleep(2000) int sum = 0 for (int i = 0 i < num + 5 i++) { long begin = System.currentTimeMillis()
switch (kind){ case 1: useString() break case 2: useStringBuilder() break case 3: useStringSpliceOneStr() break case 4: useStringBuilderSpliceOneStr() break default: return 0 }
long end = System.currentTimeMillis()
if(i > 5){ sum += (end - begin) } } return sum / num }
主方法
public class StringTest { public static final int CYCLE_NUM_BIGGER = 10_000_000 public static final int CYCLE_NUM_LOWER = 10_000 public static final String str1 = "张三" public static final String str2 = "李四" public static final String str3 = "王五" public static final String str4 = "赵六"
public static void main(String[] args) throws InterruptedException { int time = 0 int num = 5
time = executeSometime(1, num) System.out.println("String拼接 "+ CYCLE_NUM_BIGGER +" 次 , " + num + "次平均时间:" + time + " ms")
time = executeSometime(2, num) System.out.println("StringBuilder拼接 "+ CYCLE_NUM_BIGGER +" 次 , " + num + "次平均时间:" + time + " ms")
time = executeSometime(3, num) System.out.println("String拼接单个字符串 "+ CYCLE_NUM_LOWER +" 次 , " + num + "次平均时间:" + time + " ms")
time = executeSometime(4, num) System.out.println("StringBuilder拼接单个字符串 "+ CYCLE_NUM_LOWER +" 次 , " + num + "次平均时间:" + time + " ms")
}}
测试结果
测试结果如下
结果分析 第一组
10_000_000 次循环拼接 , 在循环内使用 String 和 StringBuilder 的效率是一样的!为什么呢?
使用 javap -c StringTest.class 反编译查看两个方法编译后的文件:
本文插图
可以发现 String 方法拼接字符串编译器优化后使用的就是 StringBuilder、因此用例1 和用例2 的效率是一样的 。
第二组
【字符串|昨天还在 for 循环里写加号拼接字符串的那个同事,今天已经不在了】第二组的结果就是大家喜闻乐见的了 , 由于 10_000_000 次循环String 拼接实在太慢所以我采用了 10_000 次拼接来分析 。
分析用例3:虽然编译器会对 String 拼接做优化 , 但是它每次在循环内创建 StringBuilder 对象 , 在循环内销毁 。 下次循环他有创建 。 相比较用例4在循环外创建 , 多了 n 次 new 对象、销毁对象的操作、n - 1 次将 StringBuilder 转换成 String 的操作。 效率低也是理所应当了 。
扩展
第一组的测试还有一种写法:
/** * 循环内 使用 StringBuilder 拼接字符串 , 一次循环后销毁 */ public static void useStringBuilderOut(){ StringBuilder sb = new StringBuilder() for (int i = 0 i < CYCLE_NUM_BIGGER i++) {// sb.setLength(0) sb.delete(0, sb.length()) String s = sb.append(str1).append(i).append(str2).append(i).append(str3).append(i).append(str4).toString() } }
循环外创建 StringBuilder 每次循环开始的时候清空 StringBuilder 的内容然后拼接 。 这种写法无论使用 sb.setLength(0) 还是 sb.delete(0, sb.length()) 效率都比直接在循环内使用 String / StringBuilder 慢 。 奈何才疏学浅我一直想不明白为什么他慢 。 我猜测是 new 对象的速度比重置长度慢 , 于是这样测试了以下:
public static void createStringBuider() { for (int i = 0 i < CYCLE_NUM_BIGGER i++) { StringBuilder sb = new StringBuilder() } }
public static void cleanStringBuider() { StringBuilder sb = new StringBuilder() for (int i = 0 i < CYCLE_NUM_BIGGER i++) { sb.delete(0, sb.length()) } }
但是结果是 cleanStringBuider 更快 。 让我摸不着头脑
如果有大神看到希望可以帮忙分析分析
结论
编译器会将 String 拼接优化成使用 StringBuilder , 但是还是有一些缺陷的 。 主要体现在循环内使用字符串拼接 , 编译器不会创建单个 StringBuilder 以复用
对于多次循环内拼接一个字符串的需求:StringBuilder 很快 , 因为其避免了 n 次 new 对象、销毁对象的操作 , n - 1 次将 StringBuilder 转换成 String 的操作
StringBuilder 拼接不适用于循环内每次拼接即用的操作方式 。 因为编译器优化后的 String 拼接也是使用 StringBuilder 两者的效率一样 。 后者写起来还方便...
【来源:程序员乔戈里】
声明:转载此文是出于传递更多信息之目的 。 若有来源标注错误或侵犯了您的合法权益 , 请作者持权属证明与本网联系 , 我们将及时更正、删除 , 谢谢 。邮箱地址:newmedia@xxcb.cn
推荐阅读
- 疫苗|你还在犹豫要不要接种新冠疫苗?看看科学分析怎么说
- 还在吃红烧鸡翅吗,这才是做鸡翅的王道,简单方便,老少皆宜
- 疫苗|还在犹豫打不打新冠疫苗?梁宗安:非限制人群放心打
- 圆周率|3.14π节特辑|四千年了,人类还在和圆周率较劲……
- 不管卤什么肉,2个香料一定不能放!怪不得你做出的发柴没人吃
- 备孕|还在无效备孕?低剂量NMN或能改善女性生育力!
- 虾皮你还在吃吗我也是今天刚知道的,抓紧提醒家里人还不迟
- 做排骨你还在焯水,怪不得做出来不好吃,不妨试试大厨的方法
- 航天|强强联手!三大航天技术强国中,中俄开始合作,美国还在单打独斗
- 还在买面包?5分钟教大家家制作金枪鱼面包,大人小孩都爱吃
