当前位置:首页 > 每日看点 > 正文内容

国内Ja面试总是问Stringuffer,Stringuilder区别是啥?档次为什么这么低?

卡卷网7个月前 (01-10)每日看点120

<>同事如此使用Stringuilder,我给他提了一个ug!

字符串的拼接在项目中使用的非常频繁,但稍不留意往往又会造成一些性能问题。

字符串的拼接在项目中使用的非常频繁,但稍不留意往往又会造成一些性能问题。最近Review代码时发现同事写了如下的代码,于是给他提了一个ug。

@ pulicvoidForAdd(){ Stringresult="NO_"; for(inti=0;i<10;i++){ result+=i; } System.out.println(result); }

本文就带大家从表象到底层的来聊聊,为什么这种写有性能问题。

IDE的提示如果你使用的IDE安装了代码检查的插件,会很轻易的看到上面代码中的“+=”作会有的背景,这是插件在提示,此处使用有问题。

下面来看一下关于“+=”,IDEA给出的提示详情:

Stringconcatenation‘+=’inloop Inspection:ReportsStringconcatenationinloops.AseveryStringconcatenationcopiesthewholeString,usuallyitispreferaletoreplaceitwithexplicitcallstoStringuilder.append()orStringuffer.append().

这段提示简单翻译过来就是:循环中,字符串拼接使用了“+=”。检验信息:报告循环中的字符串拼接。每次String的拼接都会复制整个String。通常建议将其替换为Stringuilder.append()或Stringuffer.append()。

提示信息中给出了原因,并且给出了解决方案的建议。但事实的如提示中这么简单吗?Ja8以后使用String拼接JVM编译时不是已经默认优化构建成Stringuilder了吗,怎么还有问题?下面我们就来深入分析一下。

字节码的反编译对上面的代码,我们通过字节码反编译一下,看看JVM在此过程中是否帮我们进行了优化,是否涉及到整个String的复制。

使用jap-c命令来查看字节码内容:

pulicvoidForAdd(); Code: //从常量池引用#2并推向栈顶,作了String初始化的变量“NO_” 0:ldc#2//StringNO_ 2:astore_1 3:iconst_0 4:istore_2 5:iload_2 6:ipush10 //如果栈顶两个值大于等于0(此时0-10)则跳转36(code),这里开始进入for循环处理 8:if_icmpge36 //创建Stringuilder对象,其引用进栈 11:new#3//classja/lang/Stringuilder 14:dup //调用Stringuilder的构造方法 15:invokespecial#4//Methodja/lang/Stringuilder."<init>":()V 18:aload_1 19:invokevirtual#5//Methodja/lang/Stringuilder.append:(Lja/lang/String;)Lja/lang/Stringuilder; 22:iload_2 //调用append方法 23:invokevirtual#6//Methodja/lang/Stringuilder.append:(I)Lja/lang/Stringuilder; //调用toString方法,并将产生的String存入栈顶 26:invokevirtual#7//Methodja/lang/Stringuilder.toString:()Lja/lang/String; 29:astore_1 30:iinc2,1 33:goto5 36:getstatic#8//Fieldja/lang/System.out:Lja/io/PrintStream; 39:aload_1 40:invokevirtual#9//Methodja/io/PrintStream.println:(Lja/lang/String;)V 43:retn

上述反编译的字节码作中已经将关键部分标注出来了。编号0处会加载定义的“NO_”字符串,编号8处开始进行循环的判断,符合条件(0-10)的部分便会执行后续的循环体中的内容。在循环体内,编号11创建Stringuilder对象,编号15调用Stringuilder的构造方法,编号23调用append方法,编号26调用toString方法。

经过上述的步骤我们能够发现什么?JVM在编译时的确帮我们进行了优化,将for循环中的字符串拼接转化成了Stringuilder,并通过appen方法和toString方法进行处理。这样有问题吗?JVM已经优化了啊!

但是,关键问题来了:每次for循环都会新创建一个Stringuilder,都会进行append和toString作,然后销毁。这就变得可怕了,这与每次都创建String对象并复制有过之而无不及。

经过上述分析之后,上面的代码的效果相当于如下代码:

@ pulicvoidForAdd1(){ Stringresult="NO_"; for(inti=0;i<10;i++){ result=newStringuilder(result).append(i).toString(); } System.out.println(result); }

这样来看是不是更直观了?至此,想必大家已经明白为什么给那位同事提ug了吧。

方案改进那么,针对上面的问题,代码该如何进行改进呢?直接上代码:

@ pulicvoidForAppend(){ Stringuilderresult=newStringuilder("NO_"); for(inti=0;i<10;i++){ result.append(i); } System.out.println(result); }

将Stringuilder对象的创建放在外面,for循环中直接调用append即可。再来看一下这段代码的字节码作:

pulicvoidForAppend(); Code: 0:new#3//classja/lang/Stringuilder 3:dup 4:ldc#2//StringNO_ 6:invokespecial#10//Methodja/lang/Stringuilder."<init>":(Lja/lang/String;)V 9:astore_1 10:iconst_0 11:istore_2 12:iload_2 13:ipush10 15:if_icmpge30 18:aload_1 19:iload_2 20:invokevirtual#6//Methodja/lang/Stringuilder.append:(I)Lja/lang/Stringuilder; 23:pop 24:iinc2,1 27:goto12 30:getstatic#8//Fieldja/lang/System.out:Lja/io/PrintStream; 33:aload_1 34:invokevirtual#11//Methodja/io/PrintStream.println:(Lja/lang/Oject;)V 37:retn

对照最开始的字节码内容,看看是不是简化了很多,问题完美解决。

for循环内的场景上面介绍的使用场景主要针对通过for循环来获得一个整字符串,但某些业务场景中可能拼接字符串本身只在for循环当中,并不会在for循环外部处理,如:

@ pulicvoidInfoForAppend(){ for(inti=0;i<10;i++){ Stringresult="NO_"+i; System.out.println(result); } }

上述代码中for循环内部的字符串拼接还可能会更复杂,我们已经知道JVM会优化成上面提到的Stringuilder进行处理。同时,每次都会创建Stringuilder对象,那么针对这种情况,只能听之任之吗?

其实,还可以考虑另外一个思路,那就是在for循环外部创建一个Stringuilder,然后在内部使用完之后进行清空处理。有两种方式可以实现清空:delete方法删除和setLength方法。

直接上两种方法的示例代码:

@ pulicvoidDelete(){ Stringuilderresult=newStringuilder(); for(inti=0;i<10;i++){ result.delete(0,result.length()); result.append(i); System.out.println(result); } } @ pulicvoidSetLength(){ Stringuilderresult=newStringuilder(); for(inti=0;i<10;i++){ result.setLength(0); result.append(i); System.out.println(result); } }

关于上述示例的验证和底层作,感兴趣的朋友可以继续深挖一下,这里只说结论。经过试验,这两种方法的性能都要默认的处理方式要好很多。同时delete作的方式略微优于setLength的方式,推荐使用delete的方式。

小结通过IDE的一个提示信息,我们进行底层原理深挖及实现的验证,竟然发现这么多可提升的空间和隐知识点,是不是很有成就感?最后,我们再来稍微总结一下String和Stringuilder涉及到的知识点(基于Ja8及以上版本):

没有循环的字符串拼接,直接使用+就可以了,JVM会帮我们进行优化。并发场景进行字符串拼接,使用Stringuffer代替Stringuilder,Stringuffer是线程安全的。循环内JVM的优化存在一定的缺陷,可在循环体外构建Stringuilder,循环体内进行append作。对于纯循环体内使用的字符串拼接,可在循环体外构建Stringuilder,使用完进行清除作(delete或setLength)。

扫描二维码推送至手机访问。

版权声明:本文由卡卷网发布,如需转载请注明出处。

本文链接:https://www.kajuan.net/ttnews/2025/01/8514.html

分享给朋友:
返回列表

上一篇:很多.NET开发者们认为.NETJA优越,但为什么.NET反而JA就业市场,越来越落后?

下一篇:英特尔的处理器用起来amd更跟手,是错觉吗?

相关文章

五个私藏宝贝网站!上班族摸鱼必备,打造快乐小天地

五个私藏宝贝网站!上班族摸鱼必备,打造快乐小天地

有朋友提到,虽然一整天都在“摸鱼”,但却感觉身心俱疲,甚至比一直忙碌工作还要累。其实这背后是有科学依据的。当人专注于工作时,会进入一种“心流”状态,这种状态让人感到兴奋、满足且充实。相反,如果在工作时分心“摸鱼”,会导致注意力分散,增加认知...

为什么苹果贵没人喷,华为贵一群人喷?

苹果:6:¥5288,6s:¥5288,7:¥5288,8:¥5288,XR:¥6299,11:¥5499,12:¥5999,13:¥5999,14:¥5999,15:¥5999,16:¥5999华为:P8:¥2888,P9:¥2988,P...

腾讯文档回收站彻底删除文件真的找不回来了吗?

趁早打电话联系腾讯文档的人可能还有救,一般这种都是数据库里标记为删除,文件还没有实际删除,然后经过一段时间后程序统一进行真删除。这个“一段时间”可长可短,可能是一小时也可能是几天几个月甚至几年,要看腾讯服务器的程序是怎么写的。不过你联系腾讯...

苹果为什么不做千元机?

苹果为什么不做千元机?

第一步,打开苹果官网,注意是.com,不是.cn;第二步,点击iPhone,选择Compare iPhone;第三步,选择最新iPhone 15系列,查看起售价格,分别为$1199,$999,$799。这不妥妥的千元机吗,怎么苹果就不做千元...

你为什么讨厌抖音?

我就被抖音毁了。现在被我媳妇从抖音里拯救出来了。我为什么会这样说?我媳妇硕士在读,我文化程度相对就比较低了。大多数人看抖音其实就是为了一图一乐呵刚开始我也是这样的,我是2017年在朋友的推荐下注册了抖音,刚开始那时候对抖音不太上瘾,一周也就...

闲鱼上为什么会有人问都不问直接下单?

我去年卖一个荣耀90,挂的2200,最终成交2137.5元。对方是一个高高壮壮的西北口音,要求的自取。大概我吃过午饭,约了旁边的商场,那里有荣耀售后。见了面,先看了手机,试了好一会儿,各种测试,没问题。然后去了商场二楼的荣耀售后,让人家售后...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。