stringbuilder用法谈谈StringBuilder的使用和细节stringbuilder的常用方法




stringbuilder用法谈谈StringBuilder的使用和细节stringbuilder的常用方法

2022-07-21 2:28:15 网络知识 官方管理员

stringbuilder用法(谈谈StringBuilder的使用和细节)(1)

前言

众所周知,在Java中String对象是不可变的。不可变性会导致一系列的效率问题,例如下面几行代码,为了生成最终的结果,I首先会和love连接生成一个IloveString对象,然后再和java.连接,再次生成一个新的String对象(这里先不讨论编译器会做优化)。

Stringstr="I";str+="love";str+="java.";System.out.println(str);复制代码

可以发现,为了生成最终的结果,会产生一系列的需要垃圾回收的中间对象,当操作的次数增加,就会导致很严重的性能问题,而StringBuilder便是专门为解决这一问题而出现的,StringBuilder可以将我们的每次操作都只在原对象上进行操作,因此便解决了由于生成中间String对象而导致的性能问题。

基本使用

StringBuilder的基本使用方法如下,我们每次需要创建一个StringBuilder对象,当需要进行字符串拼接操作时,只需要使用append方法即可。

StringBuildersb=newStringBuilder();sb.append("I");sb.append("love");sb.append("java.");System.out.println(sb);复制代码

然而其实以上两种操作,经过编译器的优化,在性能上一样的,我们可以通过javap指令来进行验证,前言中的代码我放在StringBuilderStudy这个类中,然后通过一下两步进行反编译来进行验证:

javacStringBuilderStudy.javajavap-cStringBuilderStudy复制代码

然后得到以下字节码结果,部分无关内容省去:

publicstaticvoidmain(java.lang.String[]);Code:0:ldc#2//StringI2:astore_13:new#3//classjava/lang/StringBuilder6:dup7:invokespecial#4//Methodjava/lang/StringBuilder."<init>":()V10:aload_111:invokevirtual#5//Methodjava/lang/StringBuilder.append:/StringBuilder;14:ldc#6//Stringlove16:invokevirtual#5//Methodjava/lang/StringBuilder.append:StringBuilder;19:invokevirtual#7//Methodjava/lang/StringBuilder.toString:()Ljava/lang/String;22:astore_123:new#3//classjava/lang/StringBuilder26:dup27:invokespecial#4//Methodjava/lang/StringBuilder."<init>":()V30:aload_131:invokevirtual#5//Methodjava/lang/StringBuilder.append:StringBuilder;34:ldc#8//Stringjava.36:invokevirtual#5//Methodjava/lang/StringBuilder.append:StringBuilder;39:invokevirtual#7//Methodjava/lang/StringBuilder.toString:()Ljava/lang/String;42:astore_143:getstatic#9//Fieldjava/lang/System.out:Ljava/io/PrintStream;46:aload_147:invokevirtual#10//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V50:return复制代码

仔细查看很容易发现,尽管我们使用的是普通的字符串拼接操作,但编译器会自动帮我们改成StringBuilder进行操作,最终调用toString方法,然后进行输出。然而,尽管编译器会帮我们做底层优化,我们在某些情况下仍然需要自己显示使用,最常见的一个情况就是在for循环当中,例如以下代码:

String[]strArr={"I","love","java."};Stringres="";for(Stringstr:strArr){res+=str;}System.out.println(res);复制代码

我们首先先进行反编译查看生成的字节码(有部分省略):

publicstaticvoidmain(java.lang.String[]);Code:0:iconst_31:anewarray#2//classjava/lang/String4:dup5:iconst_06:ldc#3//StringI8:aastore9:dup10:iconst_111:ldc#4//Stringlove13:aastore14:dup15:iconst_216:ldc#5//Stringjava.18:aastore19:astore_120:ldc#6//String32:iload534:iload436:if_icmpge7139:aload_340:iload542:aaload43:astore645:new#7//classjava/lang/StringBuilder48:dup49:invokespecial#8//Methodjava/lang/StringBuilder."<init>":()V52:aload_253:invokevirtual#9//Methodjava/lang/StringBuilder.append:StringBuilder;56:aload658:invokevirtual#9//Methodjava/lang/StringBuilder.append:/StringBuilder;61:invokevirtual#10//Methodjava/lang/StringBuilder.toString:()Ljava/lang/String;64:astore_265:iinc5,168:goto3271:getstatic#11//Fieldjava/lang/System.out:Ljava/io/PrintStream;74:aload_275:invokevirtual#12//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V78:return复制代码

稍微读一下,可以通过68行的goto32知道,32行便是循环的入口点,而很容易发现在循环内部,在45行处有一个new操作,说明在每次循环中为了进行字符串的拼接操作都会生成一个新的StringBuilder对象,最后再调用toString方法。这也导致了每次循环都会产生一个中间对象需要垃圾回收,影响了性能,那如果我们自己使用呢,又会是怎样?先自己写出如下代码:

String[]strArr={"I","love","java."};StringBuildersb=newStringBuilder();for(Stringstr:strArr){sb.append(str);}System.out.println(sb);复制代码

然后查看反编译生成的字节码(有删减):

publicstaticvoidmain(java.lang.String[]);Code:0:iconst_31:anewarray#2//classjava/lang/String4:dup5:iconst_06:ldc#3//StringI8:aastore9:dup10:iconst_111:ldc#4//Stringlove13:aastore14:dup15:iconst_216:ldc#5//Stringjava.18:aastore19:astore_120:new#6//classjava/lang/StringBuilder23:dup24:invokespecial#7//Methodjava/lang/StringBuilder."<init>":()V37:iload539:iload441:if_icmpge6344:aload_345:iload547:aaload48:astore650:aload_251:aload653:invokevirtual#8//Methodjava/lang/StringBuilder.append:/StringBuilder;56:pop57:iinc5,160:goto3763:getstatic#9//Fieldjava/lang/System.out:Ljava/io/PrintStream;66:aload_267:invokevirtual#10//Methodjava/io/PrintStream.println:(Ljava/lang/Object;)V70:return复制代码

仔细查看便可以发现,在这里的循环入口为37行,而循环内部也没有了生成中间StringBuilder对象的代码,只有循环外20行处我们自己进行的一次new操作。因此,尽管编译器会帮助我们做底层的优化,但是当在循环中等一些地方使用字符串拼接操作时,还是需要自己亲自使用StringBuilder对象进行操作,而对于return"I"+"love"+"java.";这种情况则可以依靠编译器的优化,而不需要自己费力去操作了。

使用细节

我们有时可能会为了方便这样使用StringBuilder进行拼接:append("("+name+")"),然而这其实是一个不好的习惯,编译器并没办法识别这种情况,即自己将括号内的拼接操作转换为多次append操作,而是会生成一个中间StringBuilder对象执行拼接操作,然后再使用toString方法,因此正确的使用的方法应该是append("(").append(name).append(")"),这里不展示反编译后的字节码了,大家感兴趣可以自己试一下。

发表评论:

最近发表
网站分类
标签列表