本文来自问题JavaStringConcatenationBestpractice?java中有很多种拼接字符串的方法,比如+运算符和StringBuilder.append方法。每种方法的优点是什么?优缺点(能适当说明各种方法的实现细节)?根据高效原则,java中字符串拼接的最佳实践是什么?字符串处理还有哪些其他最佳实践?废话不多说,直接开始,环境如下:JDK版本:1.8.0_65CPU:i74790内存:16G直接使用+拼接看下面代码:@Testpublicvoidtest(){Stringstr1="abc";Stringstr2=“def”;记录器。debug(str1+str2);}在上面的代码中,我们使用加号来连接四个字符串。这种字符串拼接方式的优点很明显:代码简单直观,但相比StringBuilder和StringBuffer在大多数情况下是低于后者的。大多数情况下,我们使用javap工具对上述代码生成的字节码进行反编译,看看编译器对这段代码做了什么。publicvoidtest();Code:0:ldc#5//Stringabc2:astore_13:ldc#6//Stringdef5:astore_26:aload_07:getfield#4//Fieldlogger:Lorg/slf4j/Logger;10:new#7//classjava/lang/StringBuilder13:dup14:invokespecial#8//Methodjava/lang/StringBuilder."":()V17:aload_118:invokevirtual#9//Methodjava/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;21:aload_222:invokevirtual#9//Methodjava/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;25:invokevirtual#10//Methodjava/lang/StringBuilder.toString:()Ljava/lang/String;28:invokeinterface#11,2//InterfaceMethodorg/slf4j/Logger.debug:(Ljava/lang/String;)V33:return从反编译的结果看,其实是字符Strings使用+运算符连接。编译器会在编译阶段优化代码使用StringBuilder类,调用append方法进行字符串拼接。最后,调用toString方法。这样看来,一般情况下似乎也算是直截了当了。使用+,反正编译器会帮我优化使用StringBuilder?StringBuilder源码分析答案自然不可能,原因就在于StringBuilder类内部做了什么。我们看一下StringBuilder类的构造函数publicStringBuilder(){super(16);}publicStringBuilder(intcapacity){super(capacity);}publicStringBuilder(Stringstr){super(str.length()+16);append(str);}publicStringBuilder(CharSequenceseq){this(seq.length()+16);append(seq);}StringBuilder提供了4个默认构造函数,除了无参构造函数外,还提供了另外3个重载版本,以及内部调用了父类的super(intcapacity)构造方法。它的父类是AbstractStringBuilder,构造方法如下:AbstractStringBuilder(intcapacity){value=newchar[capacity];}可见,StringBuilder内部实际上使用char数组来存储数据(String、StringBuffer也是),其中容量值指定数组的大小。结合StringBuilder的无参构造函数,可以知道默认大小是16个字符。也就是说,如果要拼接的字符串总长度不小于16个字符,直接拼接和我们手动拼接StringBuilder没有太大区别,但是我们可以构造StringBuilder类来指定拼接的大小数组以避免分配太多内存。下面我们看看StringBuilder.append方法内部做了什么:@OverridepublicStringBuilderappend(Stringstr){super.append(str);returnthis;}直接调用的父类的append方法:publicAbstractStringBuilderappend(Stringstr){if(str==null)returnappendNull();intlen=str.length();ensureCapacityInternal(count+len);str.getChars(0,len,value,count);count+=len;returnthis;}this内部调用了ensureCapacityInternal方法方法,当拼接后的字符串总大小大于内部数组值大小时,必须先扩容再进行拼接。扩展代码如下:voidexpandCapacity(intminimumCapacity){intnewCapacity=value.length*2+2;if(newCapacity-minimumCapacity<0)newCapacity=minimumCapacity;if(newCapacity<0){if(minimumCapacity<0)//overflowthrownewOutOfMemoryError();newCapacity=Integer.MAX_VALUE;}value=Arrays.copyOf(value,newCapacity);}StringBuilder扩容时增加容量可怕的是是当前容量的两倍+2如果当时没有指定容量构建的时候,很可能扩容后会浪费大量的内存空间。其次,展开之后,还会调用Arrays.copyOf方法。该方法将扩容前的数据复制到扩容后的空间中。这样做的原因是:StringBuilder内部使用char数组来存储数据,而java数组是不可扩展的,所以只能重新申请一块内存空间,将已有的数据复制到新的空间。这里终于调用了System.arraycopy方法进行复制。这是一种本地方法。底层直接操作内存,比我们用循环复制要好,但申请大量内存空间和复制数据的影响也不容忽视。使用+拼接,使用StringBuilder进行比较@Testpublicvoidtest(){Stringstr="";for(inti=0;i<10000;i++){str+="asjdkla";}}上面的代码优化后等价于:@testpublicvoidtest(){Stringstr=null;for(inti=0;i<10000;i++){str=newStringBuilder().append(str).append("asjdkla").toString();}}可以看到一个glancetocreateStringBuilder对象太多,每次循环后str越来越大,导致每次申请的内存空间越来越大,当str的长度大于16时,每次都需要扩容两次!实际上toString方法在创建String对象时,会调用Arrays.copyOfRange方法来复制数据。这时候相当于扩容2倍,每次执行都要复制3次数据。成本相当高。publicvoidtest(){StringBuildersb=newStringBuilder("asjdkla".length()*10000);for(inti=0;i<10000;i++){sb.append("asjdkla");}Stringstr=sb.toString();这段代码的执行时间在我的机器上是0ms(不到1ms)和1ms,而上面的代码大约是380ms!效率上的差异是相当明显的。上面同样的代码,当循环次数调整为1000000次时,在我的机器上,指定容量时大约需要20ms,不指定容量时大约需要29ms。尽管这种差距与直接使用+运算符相比有很大的改进(并且循环次数增加了100倍),但它仍然会触发多次缩放和复制。更改上面的代码以使用StringBuffer。在我的机器上,大约需要33毫秒。这是因为StringBuffer为了保证线程安全,在大部分方法中加入了synchronized关键字,一定程度上降低了执行效率。.使用String.concat拼接现在看这段代码:@Testpublicvoidtest(){Stringstr="";for(inti=0;i<10000;i++){str.concat("asjdkla");}}这段代码使用了TheString.concat方法被使用。在我的机器上,执行时间约为130毫秒。直接相加虽然好很多,但是比起使用StringBuilder,显得没啥用,实在是太多了。事实上,它不是。在很多情况下,我们只需要连接两个字符串,而不是拼接多个字符串。这时候使用String.concat方法比StringBuilder更加简洁高效。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);}上面这段是String.concat的源码。该方法中调用了一次Arrays.copyOf,指定了len+otherLen,相当于分配了一次内存空间,分别从str1和str2中复制数据。但是,如果使用StringBuilder并指定capacity,相当于分配一次内存空间,分别从str1和str2复制数据。***因为调用了toString方法,数据又被复制了一遍。结束语现在根据上面的分析和测试,我们可以知道:Java中的字符串拼接不要直接使用+拼接。在使用StringBuilder或StringBuffer时,尽可能准确地预估容量,并在构造时指定,避免内存浪费和频繁扩容复制。当没有线程安全问题时使用StringBuilder,否则使用StringBuffer。两个字符串的拼接直接调用String.concat性能***。其他关于String的最佳实践在使用equals的时候,总是把可以判断不为空的变量写在左边,比如使用".equals(str)"来判断空字符串,避免空指针异常。第二穴是用来挤***穴的。使用str!=null&&str.length()!=0判断空串,比***点效率更高。当需要将其他对象转换为字符串对象时,使用String.valueOf(obj)而不是直接调用obj.toString()方法,因为前者已经检查了空值,不会抛出空指针异常。使用String.format()方法格式化输出字符串。在JDK7及以上版本中,switch结构中可以使用字符串,所以更多的比较,用switch代替if-else。暂时能想到的就这些了。请帮我添加...