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 ...
随机推荐
- JAVASE(九)面向对象特性之 : 继承性、方法重写、关键字super、
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 1.继承性 1.1 为什么要类的继承性?(继承性的好处) ①减少了代码的冗余,提高了代码的复用性:②更好 ...
- Java实现 蓝桥杯VIP 算法提高 笨小猴
算法提高 笨小猴 时间限制:1.0s 内存限制:256.0MB 问题描述 笨小猴的词汇量很小,所以每次做英语选择题的时候都很头疼.但是他找到了一种方法,经试验证明,用这种方法去选择选项的时候选对的几率 ...
- 第五届蓝桥杯JavaB组省赛真题
解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论 题目1.武功秘籍 小明到X山洞探险,捡到一本有破损的武功秘籍(2000多页!当然是伪造的).他注意到:书的第10页和第11页在同一张纸上,但第 ...
- java实现括号的匹配
括号的匹配 下面的代码用于判断一个串中的括号是否匹配 所谓匹配是指不同类型的括号必须左右呼应,可以相互包含,但不能交叉 例如: -(-[-]-)- 是允许的 -(-[-)-]- 是禁止的 对于 mai ...
- java实现第三届蓝桥杯DNA对比
DNA对比 脱氧核糖核酸即常说的DNA,是一类带有遗传信息的生物大分子.它由4种主要的脱氧核苷酸(dAMP.dGMP.dCMT和dTMP)通过磷酸二酯键连接而成.这4种核苷酸可以分别记为:A.G.C. ...
- Jmeter让压测随时做起来(转载)
为什么要压测 这个问题问的其实挺没有必要的,做开发的同学应该都很清楚,压测的必要性,压力测试主要目的就是让我们在上线前能够了解到我们系统的承载能力,和当前.未来系统压力的提升情况,能够评估出当前系统的 ...
- Yangcs从简书搬回来了
追求更加畅快淋漓的书写体验: 简书地址: http://www.jianshu.com/users/9913981cb400/latest_articles. Yangcs在简书[2016] 简书已经 ...
- 重学 Java 设计模式:实战组合模式(营销差异化人群发券,决策树引擎搭建场景)
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 小朋友才做选择题,成年人我都要 头几年只要群里一问我该学哪个开发语言,哪个语言最好. ...
- chrome浏览器版本与驱动不匹配问题的解决办法
1.浏览器与驱动如何匹配才不会报错 使用selenium模块的webdriver打开谷歌浏览器时常遇到这样的错误提示: selenium.common.exceptions.WebDriverExce ...
- CMDB 和自动化运维
目录 传统运维和自动化运维的对比 CMDB CMDB 的几种实现方式 传统运维和自动化运维的对比 1.企业中,项目的发布流程 产品经理调研需求 -->三方开会讨论(开发,产品,运维,测试) -– ...