首页 » 编写高质量代码:改善Java程序的151个建议 » 编写高质量代码:改善Java程序的151个建议全文在线阅读

《编写高质量代码:改善Java程序的151个建议》建议56:自由选择字符串拼接方法

关灯直达底部

对一个字符串进行拼接有三种方法:加号、concat方法及StringBuilder(或StringBuffer,由于StringBuffer的方法与StringBuilder相同,文中不再赘述)的append方法,其中加号是最常用的,其他两种方式偶尔会出现在一些开源项目中,那这三者之间有什么区别吗?我们来看下面的例子:


//加号拼接

str+="c";

//concat方法连接

str=str.concat("c");


上面是两种不同的字符串拼接方式,循环5万次后再检查其执行的时间,加号方式的执行时间是1438毫秒,而concat方法的执行时间是703毫秒,时间相差1倍,如果使用StringBuilder方式,执行时间会更少,其代码如下:


public static void doWithStringBuffer(){

StringBuilder sb=new StringBuilder("a");

for(int i=0;i<50000;i++){

sb.append("c");

}

String str=sb.toString();

}


StringBuffer的append方法的执行时间是0毫秒,说明时间非常非常短暂(毫秒不足以计时,读者可以使用纳秒进行计算)。这个实验也说明在字符串拼接方式中,append方法最快,concat方法次之,加号最慢,这是为何呢?

(1)“+”方法拼接字符串

虽然编译器对字符串的加号做了优化,它会使用StringBuilder的append方法进行追加,按道理来说,其执行时间也应该是0毫秒,不过它最终是通过toString方法转换成String字符串的,例子中“+”拼接的代码与如下代码相同:


str=new StringBuilder(str).append("c").toString();


注意看,它与纯粹使用StringBuilder的append方法是不同的:一是每次循环都会创建一个StringBuilder对象,二是每次执行完毕都要调用toString方法将其转换为字符串——它的执行时间就是耗费在这里了!

(2)concat方法拼接字符串

我们从源码上看一下concat方法的实现,代码如下:


public String concat(String str){

int otherLen=str.length();

//如果追加的字符串长度为0,则返回字符串本身

if(otherLen==0){

return this;

}

//字符数组,容纳的是新字符串的字符

char buf=new char[count+otherLen];

//取出原始字符串放到buf数组中

getChars(0,count, buf,0);

//追加的字符串转化成字符数组,添加到buf中

str.getChars(0,otherLen, buf, count);

//复制字符数组,产生一个新的字符串

return new String(0,count+otherLen, buf);

}


其整体看上去就是一个数组拷贝,虽然在内存中的处理都是原子性操作,速度非常快,不过,注意看最后的return语句,每次的concat操作都会新创建一个String对象,这就是concat速度慢下来的真正原因,它创建了5万个String对象呀!

(3)append方法拼接字符串

StringBuilder的append方法直接由父类AbstractStringBuilder实现,其代码如下:


public AbstractStringBuilder append(String str){

//如果是null值,则把null作为字符串处理

if(str==null)str="null";

int len=str.length();

//字符串长度为0,则返回自身

if(len==0)return this;

int newCount=count+len;

//追加后的字符数组长度是否超过当前值

if(newCount>value.length)

expandCapacity(newCount);//加长,并做数组拷贝

//字符串复制到目标数组

str.getChars(0,len, value, count);

count=newCount;

return this;

}


看到没,整个append方法都在做字符数组处理,加长,然后数组拷贝,这些都是基本的数据处理,没有新建任何对象,所以速度也就最快了!注意:例子中是在最后通过StringBuffer的toString返回了一个字符串,也就是说在5万次循环结束后才生成了一个String对象。

三者的实现方法不同,性能也就不同,但并不表示我们一定要使用StringBuilder,这是因为“+”非常符合我们的编码习惯,适合人类阅读,两个字符串拼接,就用加号连一下,这很正常,也很友好,在大多数情况下我们都可以使用加号操作,只有在系统性能临界(如在性能“增之一分则太长”的情况下)的时候才可以考虑使用concat或append方法。而且,很多时候系统80%的性能是消耗在20%的代码上的,我们的精力应该更多的投入到算法和结构上。

注意 适当的场景使用适当的字符串拼接方式。