转载请注明出处,如有错误,麻烦指出~谢
开始先来个面试题:
使用+可以连接两个字符串(string对象),那么,是怎么样进行连接的?
当我们使用“+”来拼接两个字符串时,比如String str = str1+str2会进行如下操作:1.调用String.valueOf(Obj),其中obj为String类型的变量,如上面提到的str1和str2,String.valueOf()的原理为:return obj == null?"null":obj.toString();
2.产生StringBuilder,调用StringBuilder(str1)的构造方法,把StringBuilder初始化,长度为str1.length+16,并且调用append(str1);
3.继续调用append(str2);
4.最后调用StringBuilder.toString()返回结果
所以最后的结论应该为:StringBuilder.append(str1).append(str2).toString()。应该注意的是StringBuffer和StringBuilder的扩容策略:
当字符串缓冲区容量不足时,原有容量将会加倍,以新的容量来申请内存空间,建立新的char数组,然后将原数组中的内容复制到这个新的数组当中。
因此,对于大对象的扩容会涉及大量的内存复制操作。所以,如果能够预先评估StringBuilder或StringBuffer的大小,将能够有效的节省这些操作,从而提高系统的性能。
这里在提及一些字符串拼接方式和性能:
1.使用+拼接两个字符串
如果你用”+”来连接固定长度的字符串,可能性能上会稍受影响,但是如果你是在循环中来”+”多个串的话,性能将 指数倍的下降
如下:public void implicitUseStringBuilder(String[] values) {
String result = "";
for (int i = 0 ; i < values.length; i ++) {
result += values[i];
}
System.out.println(result);
}
使用javac编译,使用javap查看
public void implicitUseStringBuilder(java.lang.String[]);
Code:
0: ldc #11 // String
2: astore_2
3: iconst_0
4: istore_3
5: iload_3
6: aload_1
7: arraylength
8: if_icmpge 38
11: new #5 // class java/lang/StringBuilder
14: dup
15: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
18: aload_2
19: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: aload_1
23: iload_3
24: aaload
25: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_2
32: iinc 3, 1
35: goto 5
38: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
41: aload_2
42: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
45: return
其中8: if_icmpge 38 和35: goto 5构成了一个循环。8: if_icmpge 38的意思是如果JVM操作数栈的整数对比大于等于(i < values.length的相反结果)成立,则跳到第38行(System.out)。35: goto 5则表示直接跳到第5行。
但是这里面有一个很重要的就是StringBuilder对象创建发生在循环之间,也就是意味着有多少次循环会创建多少个StringBuilder对象,这样明显不好。代码应该这样写:
public void explicitUseStringBuider(String[] values) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < values.length; i ++) {
result.append(values[i]);
}
}
2.使用concat连接两个字符串
源码:public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
char数组的拷贝再重新生成String,其实通过反编译知道concat()方法使用了StringBuilder,concat()的性能应该和StringBuilder的一样好,但是由于额外的创建StringBuilder和做.append(str).append(str).toString()的操作,使得concat的性能会受到一些影响
3.使用StringBuilder来拼接
源码: public StringBuilder append(String str) {
super.append(str);
return this;
}
父类的append方法实现
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length(); //连接的字符串长度
ensureCapacityInternal(count + len); //重新给value(char[])数组赋值,数组长度变为为当前字符串长度+传入的新字符串长度之和,值为当前的值
str.getChars(0, len, value, count);//为value数组赋值,值为当前字符串+新字符串
count += len;//标记后移
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));//char[] value;
}
}
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
4.使用StringBuffer(线程安全)
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
综合以上可以发现,StringBuffer是线程安全的,有加锁开销,效率略低。StringBuilder非线程安全,不用加锁,效率更高。一般字符串相加不会有多线程操作,
所以推荐使用StringBuilder。另外,事实上我们在java里写的字符串相加的操作被编译后都是通过new一个StringBuffer或StringBuilder对象来操作的,所以不会产生大量的String对象
附上测试代码:(注意导包)
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StringTest {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Test
public void testPlus() {
String s = "";
long ts = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s = s + String.valueOf(i);
}
long te = System.currentTimeMillis();
logger.info("+ cost {} ms", te - ts);
}
@Test
public void testConcat() {
String s = "";
long ts = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s = s.concat(String.valueOf(i));
}
long te = System.currentTimeMillis();
logger.info("concat cost {} ms", te - ts);
}
@Test
public void testStringBuffer() {
StringBuffer sb = new StringBuffer();
long ts = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
sb.append(String.valueOf(i));
}
sb.toString();
long te = System.currentTimeMillis();
logger.info("StringBuffer cost {} ms", te - ts);
}
@Test
public void testStringBuilder() {
StringBuilder sb = new StringBuilder();
long ts = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
sb.append(String.valueOf(i));
}
sb.toString();
long te = System.currentTimeMillis();
logger.info("StringBuilder cost {} ms", te - ts);
}
}
运行结果:
[06 13:54:27,336 INFO ] [main] StringTest - StringBuilder cost 8 ms
[06 13:54:27,729 INFO ] [main] StringTest - + cost 391 ms
[06 13:54:27,731 INFO ] [main] StringTest - StringBuffer cost 1 ms
[06 13:54:27,904 INFO ] [main] StringTest - concat cost 172 ms