当前位置: 首页 > 科技观察

教妹子学Java:字符串拼接

时间:2023-03-18 11:16:00 科技观察

》哥,你让我看看在《Java 开发手册》上有这样一段话:在循环体中,最好使用StringBuilder的append()方法,而不是+为什么会这样?”三姐疑惑的问道。“好吧,三姐,我慢慢跟你说。”我回答。三姐在学习的过程中能不断地发现问题,这让我很开心。其实很多时候,我们不应该只把知识点留在心里,还要问问自己为什么,只有迈出这一步,才能真正成长起来。“+运算符其实是Java在编译的时候重新解释的。换句话说,+运算符是一种语法糖,让字符串拼接更容易。”一边给三妹解释,一边在IntellijIDEA中敲出如下代码。classDemo{publicstaticvoidmain(String[]args){Stringchenmo="silence";Stringwanger="Wanger";System.out.println(chenmo+wanger);}}Java8环境下,使用javap-cDemo.class反编译后字节码,可以看到如下内容:Compiledfrom"Demo.java"classDemo{Demo();Code:0:aload_01:invokespecial#1//Methodjava/lang/Object."":()V4:returnpublicstaticvoidmain(java.lang.String[]);代码:0:ldc#2//弦静2:astore_13:ldc#3//弦王25:astore_26:getstatic#4//字段java/lang/System.out:Ljava/io/PrintStream;9:new#5//classjava/lang/StringBuilder12:dup13:invokespecial#6//Methodjava/lang/StringBuilder."":()V16:aload_117:invokevirtual#7//Methodjava/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;20:aload_221:invokevirtual#7//Methodjava/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;24:invokevirtual#8//Methodjava/lang/StringBuilder.toString:()Ljava/lang/String;27:invokevirtual#9//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V30:return}“看,三妹,多了一个关键字,类类型是java/lang/StringBuilder”我指着标记为9的那一行三妹说,“这个意味着已经创建了一个新的StringBuilder对象。”“再看标号为17的那一行,这是一条invokevirtual指令,用来调用对象的方法,即StringBuilder对象的append()方法。”意思是将字符串chenmo添加到StringBuilder对象中。”“往下看,标号为21的那行又调用了append()方法,意思是将字符串wanger添加到StringBuilder对象Hit中。”如果用Java代码表达,看起来像这样:());}}“哦,编译时把”+”操作符换成了StringBuilder的append()方法。”三姐恍然大悟。字节码指令完全不同。”我说。同样的代码,在Java11环境下,字节码指令如下:Compiledfrom"Demo.java"publicclasscom.itwanger.thirtyseven.Demo{publiccom.itwanger.thirtyseven.Demo();Code:0:aload_01:invokespecial#1//方法java/lang/Object."":()V4:returnpublicstaticvoidmain(java.lang.String[]);代码:0:ldc#2//String2:astore_13:iconst_04:istore_25:iload_26:bipush108:if_icmpge4111:new#3//classjava/lang/String14:dup15:ldc#4//Stringsilence17:invokespecial#5//Methodjava/lang/String."":(Ljava/lang/String;)V20:astore_321:ldc#6//字符串23:astore425:aload_126:aload_327:aload429:invokedynamic#7,0//InvokeDynamic#0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;34:astore_135:iinc2,138:goto541:return}看标为29的行,字节码指令是invokedynamic,它允许应用程序级代码确定方法分析,so-称为应用程序级代码的实际上是一种方法——称为引导程序方法(BootstrapMethod),简称BSM,BSM会返回一个CallSite(调用点)对象,这个对象与invokedynamic指令挂钩。以后执行这条invokedynamic指令时,不会创建新的CallSite对象。CallSite实际上是一个MethodHandle(方法句柄)的持有者,指向调用处实际执行的方法——这次是StringConcatFactory.makeConcatWithConstants()方法。“哥,你别说了,我听不懂你的意思了。”三姐打断了我的话。”嗯,在Java9之后,JDK使用了另一种方式来动态解释+操作符。具体的实现方法在字节码指令层面已经看不到了,所以我会继续用Java8来解释。””回到段落on《Java 开发手册》:在循环体中,最好使用StringBuilder的append()方法,而不是+操作符在循环体中拼接字符串,原因是如果在循环体中使用了+操作符,会产生大量的StringBuilder对象,不仅会占用更多的内存空间,还会导致Java虚拟机进行垃圾回收的方式不同,从而降低程序的性能。”更好的写法是在循环对象外新建一个StringBuilder,然后在循环体中使用append()方法添加字符串:classDemo{publicstaticvoidmain(String[]args){StringBuildersb=newStringBuilder();for(inti=1;i<10;i++){Stringchenmo="silence";Stringwanger="Wanger";sb.append(chenmo);sb.append(wanger);}System.out.println(sb);}}做一个小测试。首先,在for循环中使用“+”运算符。Stringresult="";for(inti=0;i<100000;i++){result+="666";}其次,在for循环外新建StringBuilder,在循环内使用append()方法。StringBuildersb=newStringBuilder();for(inti=0;i<100000;i++){sb.append("666");}“这两个小测试分别需要多长时间?三姐你来跑吧。”“哇,第一个小测试的执行时间是6212毫秒,第二个只用了不到1毫秒,差距太大了!”三妹说。“是啊,现在你明白为什么了吗?”我说。“是的,兄弟,是这样的。”“好的,三姐,我们来看看StringBuilder类的append()方法的源码吧!”publicStringBuilderappend(Stringstr){super.append(str);returnthis;}代码并不重要。再看父类AbstractStringBuilder的append()方法:publicAbstractStringBuilderappend(Stringstr){if(str==null)returnappendNull();intlen=str.length();ensureCapacityInternal(count+len);str.getChars(0,len,value,count);count+=len;returnthis;}1)判断拼接后的字符串是否为null,如果是,则将其视为字符串“null”。appendNull()方法源码如下:privateAbstractStringBuilderappendNull(){intc=count;ensureCapacityInternal(c+4);finalchar[]value=this.value;value[c++]='n';value[c++]='你';value[c++]='l';value[c++]='l';count=c;returnthis;}2)获取字符串的长度。3)ensureCapacityInternal()方法源码如下:}}由于内部是用数组实现的,所以需要判断拼接字符数组的长度是否超过当前数组的长度。如果是,先扩充数组,再将原值复制到新数组中。4)将连接后的字符串str复制到目标数组值中。str.getChars(0,len,value,count)5)更新数组的长度计数。“说到StringBuilder,就不得不提StringBuffer,两者就像双胞胎,该有的都有,但大哥StringBuffer是线程安全的,因为多吸了两口新鲜空气。”我说,“它包含的方法基本上是加了synchronized关键字进行同步。”publicsynchronizedStringBufferappend(Stringstr){toStringCache=null;super.append(str);returnthis;}”除了+运算符,StringBuilder的append()和StringBuilder方法,还有其他拼接字符串的方法吗?”三妹问道。“是的,比如String类的concat()方法有点像StringBuilder类的append()方法。”Stringchenmo="silent";Stringwanger="王二";System.out.println(chenmo.concat(wanger));你可以看看concat()方法的源代码。publicStringconcat(Stringstr){intotherLen=str.length();if(otherLen==0){returnthis;}intlen=value.length;charbuf[]=Arrays.copyOf(value,len+otherLen);str.getChars(buf,len);returnnewString(buf,true);}1)如果拼接后的字符串长度为0,则返回拼接前的字符串。2)将原字符串的字符数组值复制到变量buf数组中。3)将拼接后的字符串str复制到字符数组buf中,并返回一个新的字符串对象。我一行一行地给三姐解释。“与+运算符相比,concat()方法在字符串为null时会抛出NullPointerException,而“+”运算符会将null视为“null”字符串。如果拼接后的字符串是空字符串(""),那么concat效率更高,毕竟不需要newStringBuilder对象。如果要拼接的字符串很多,concat()的效率会降低,因为会创建越来越多的字符串对象。“还有吗?”三姐似乎对弦乐拼接很感兴趣。“是的当然。”String类有一个静态方法join()可以以这种方式使用。Stringchenmo="silence";Stringwanger="Wanger";Stringcmower=String.join("",chenmo,wanger);System.out.println(cmower);第一个参数为字符串连接字符,例如:Stringmessage=String.join("-","王二","太特别了","有意思");输出结果为:王二-太特别-有趣了。看一下join方法的源码:for(CharSeelements){joiner.add(cs);}returnjoiner.toString();}新建一个对象StringJoiner,然后通过for-each循环添加可变参数,最后调用toString()方法返回String.“实际工作中,经常使用org.apache.commons.lang3.StringUtils的join()方法进行字符串拼接。”Stringchenmo="silent";Stringwanger="王二";StringUtils.join(chenmo,wanger);此方法不必担心NullPointerException。StringUtils.join(null)=nullStringUtils.join([])=""StringUtils.join([null])=""StringUtils.join(["a","b","c"])="abc"StringUtils.join([null,"","a"])="a"我们看一下源码:publicstaticStringjoin(finalObject[]array,Stringseparator,finalintstartIndex,finalintendIndex){if(array==null){returnnull;}if(separator==null){separator=EMPTY;}finalStringBuilderbuf=newStringBuilder(noOfItems*16);for(inti=startIndex;istartIndex){buf.append(分隔符);}if(array[i]!=null){buf.append(array[i]);}}returnbuf.toString();}内部仍然使用StringBuilder。“好了,三妹,关于字符串拼接的知识点就到此为止了。注意,Java9之后,+运算符的解释和以前发生了变化,字节码指令也不一样了,等你以后再详细说在学习了字节码指令之后。”我说。“好了,兄弟你休息吧,我再把这些例子跑一遍。”三姐说道。本文转载自微信?“沉默王二”,可通过以下二维码关注。转载本文请联系沉默王二公众号。