StringBuider 在什么条件下、如何使用效率更高?
声明:本文首发于博客园,作者:后青春期的Keats;地址:https://www.cnblogs.com/keatsCoder/ 转载请注明,谢谢!
引言
都说 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 的效率是一样的。
第二组
第二组的结果就是大家喜闻乐见的了,由于 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 两者的效率一样。后者写起来还方便...
StringBuider 在什么条件下、如何使用效率更高?的更多相关文章
- AliIAC 智能音频编解码器:在有限带宽条件下带来更高质量的音频通话体验
随着信息技术的发展,人们对实时通信的需求不断增加,并逐渐成为工作生活中不可或缺的一部分.每年海量的音视频通话分钟数对互联网基础设施提出了巨大的挑战.尽管目前全球的互联网用户绝大多数均处于良好的网络状况 ...
- Effective Java 第三版——69. 仅在发生异常的条件下使用异常
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- Atitit 如何创新 创新只有在两种条件下发生:自由、效率。
Atitit 如何创新 创新只有在两种条件下发生:自由.效率. 创新是如何发生的呢? 创新只有在两种条件下发生:自由.效率.在自由的环境下,对效率的追逐等于创新.如果你不自由,你的思想不够开阔,你脑洞 ...
- java中继承条件下构造方法的执行过程
继承条件下构造方法的调用规则如下: 情况1:如果子类的构造方法中没有通过super显式调用父类的有参构造方法,也没有通过this显式调用自身的其他构造方法,则系统会默认先调用父类的无参构造方法.在这种 ...
- 使用n2n在没有公网IP条件下访问树莓派
实现:在树莓派2和客户机都没有公网IP条件下实现远程访问控制 不足:暂时没实现网页代理 因为校园网环境没有公网IP,无法直接访问树莓派.之前有想过SSH反向代理:使用VPN,ddns(花生壳.no-i ...
- .NET DLL 保护措施详解(五)常规条件下的破解
为了证实在常规手段破解下能有效保护程序核心功能(演示版本对AES加解密算法及数据库的密钥(一段字符串)进行了保护),特对此DLL保护思路进行相应的测试,包含了反编译及反射测试,看是否能得到AES加解密 ...
- 《Python CookBook2》 第四章 Python技巧 - 若列表中某元素存在则返回之 && 在无须共享引用的条件下创建列表的列表
若列表中某元素存在则返回之 任务: 你有一个列表L,还有一个索引号i,若i是有效索引时,返回L[i],若不是,则返回默认值v 解决方案: 列表支持双向索引,所以i可以为负数 >>> ...
- 如何在已安装Python条件下,安装Anaconda,,并将原有Python添加到Anaconda中
在安装Anaconda之前,有的已经安装过一个Python版本了,但是又不想删除这个Python版本,该怎么办呢? 概括:轻松两步--在系统环境变量中找到对应之前安装Python的路径并删除:直接将你 ...
- C#本质论第四版-1,抄书才能看下去,不然两三眼就看完了,一摞书都成了摆设。抄下了记忆更深刻
C#本质论第四版-1,抄书才能看下去,不然两三眼就看完了,一摞书都成了摆设.抄下了记忆更深刻 本书面向的读者 写作本书时,我面临的一个挑战是如何持续吸引高级开发人员眼球的同时,不因使用assembly ...
随机推荐
- java实现蔬菜价格计算
** 蔬菜价格计算** 计算蔬菜总价 为了丰富群众菜篮子,平抑菜价,相关部分组织了蔬菜的调运.今某箱中有多个品种的蔬菜.蔬菜的单价(元/公斤)存放在price数组中,蔬菜的重量(公斤)存放在weigh ...
- java实现第七届蓝桥杯生日蜡烛
生日蜡烛 生日蜡烛 某君从某年开始每年都举办一次生日party,并且每次都要吹熄与年龄相同根数的蜡烛. 现在算起来,他一共吹熄了236根蜡烛. 请问,他从多少岁开始过生日party的? 请填写他开始过 ...
- Linux RPM管理命令
RPM安装 rpm -ivh 包全名,其中,-i 表示安装,-v 表示显示详细信息,-h 表示显示进度 手动安装会有很多包依赖需要解决,如果是模块依赖,可以使用rpmfind进行查询解决 RPM升级 ...
- Java并发编程实战总结 (一)
前提 首先该场景是一个酒店开房的业务.为了朋友们阅读简单,我把业务都简化了. 业务:开房后会添加一条账单,添加一条房间排期记录,房间排期主要是为了房间使用的时间不冲突.如:账单A,使用房间1,使用时间 ...
- 如何监控 Linux 服务器状态?
Linux 服务器我们天天打交道,特别是 Linux 工程师更是如此.为了保证服务器的安全与性能,我们经常需要监控服务器的一些状态,以保证工作能顺利开展. 本文介绍的几个命令,不仅仅适用于服务器监控, ...
- C#基础篇——委托
前言 在本章中,主要是借机这个C#基础篇的系列整理过去的学习笔记.归纳总结并更加理解透彻. 在.Net开发中,我们经常会遇到并使用过委托,如果能灵活的掌握并加以使用会使你在编程中游刃有余,然后对于很多 ...
- 使用 xshell 登录 Windows 的 linux 子系统
1 配置 SSH Server # 卸载自带的 ssh sudo apt-get remove openssh-server # 安装 ssh sudo apt-get install openssh ...
- MySQL触发器的详细教学与综合分析
所有知识体系文章,GitHub已收录,欢迎老板们前来Star! GitHub地址: https://github.com/Ziphtracks/JavaLearningmanual MySQL触发器 ...
- [xDebug]Xdebug和Sublime调试PHP代码
安装xdebug 省略... 配置sublime 要调试某一个项目,首先得把这个项目在sublime下保存成一个project sublime->project->save project ...
- [51nod 1847]奇怪的数学题
[ 51nod 1847 ]奇怪的数学题 题目 点这里看题目. 分析 是挺奇怪的...... 以下定义质数集合为\(P\),\(p_i\)为第\(i\)个质数. 定义\(mp(x)\) ...