1."+"操作符和append方法
在我们初学String的时候,无数人跟我们说过
尽量不要使用 "+" 拼接字符串,效率不好,应该使用append,你自己循环拼接测一测时间就知道了 。
我们不妨来循环一百万次,看一看它们的区别。
// 1.for 循环中使用”+”号操作符。
long startTime1 = System.currentTimeMillis();
String a = "";
for (int i = 0; i < 1000000; i++) {
a += "对";
}
long endTime1 = System.currentTimeMillis();
System.out.println(endTime1 - startTime1);
// 2.for 循环中使用 append。
long startTime2 = System.currentTimeMillis();
StringBuilder b = new StringBuilder();
for (int i = 0; i < 1000000; i++) {
b.append("对");
}
long endTime2 = System.currentTimeMillis();
System.out.println(endTime2 - startTime2);
这两段代码分别会耗时多长时间呢?在我的笔记本上测试出的结果是:
1)第一段代码耗时 146611 毫秒
2)第二段代码耗时 20 毫秒
可以看到确实差距巨大,这是为什么呢?
我们再看一下这段代码:
String a = "数码";
String b = "产品";
System.out.println(a + b);
反编译之后,第三行是这样的:
System.out.println((new StringBuilder(String.valueOf(a))).append(b).toString());
原来JVM在编译时会把“+”号操作符替换成 StringBuilder 的 append 方法。
现在我们终于明白一开始的那两段代码,耗时差距为什么那么大了。
- 第一段代码,每次循环都会new一个StringBuilder,循环中 "+" 拼接被断成了十万条语句,那自然就会new十万次,创建了大量的 StringBuilder 对象。
- 第二段代码至始至终只有一个 StringBuilder 对象。
至于StringBuffer,它和StringBuilder一样,只不过StringBuffer是线程安全的,因为它的 append 方法比 StringBuilder 多了一个关键字 synchronized。
2.concat 方法
String a = "中文";
String b = "Chinese";
System.out.println(a.concat(b));
这看上去和append方法好像没什么区别,那就只能看他们各自的源码了。
1. append方法的源码:
@Override
public StringBuilder append(CharSequence s) {
super.append(s);
return this;
}
这看不出什么,继续深入看父类AbstractStringBuilder 的 append 方法:
public AbstractStringBuilder append(String str) {
if (str == null) {
return appendNull();
}
int len = str.length();
ensureCapacityInternal(count + len);
putStringAt(count, str);
count += len;
return this;
}
首先,判断拼接的字符串是否为null,如果是null,返回appendNull()方法。
那么appendNull()
方法是怎么实现的呢:
private AbstractStringBuilder appendNull() {
ensureCapacityInternal(count + 4);
int count = this.count;
byte[] val = this.value;
if (isLatin1()) {
val[count++] = 'n';
val[count++] = 'u';
val[count++] = 'l';
val[count++] = 'l';
} else {
count = StringUTF16.putCharsAt(val, count, 'n', 'u', 'l', 'l');
}
this.count = count;
return this;
}
原来appendNull
的作用就是返回字符串“null”。
再看 ensureCapacityInternal
是用来干什么的:
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder;
if (minimumCapacity - oldCapacity > 0) {
value = Arrays.copyOf(value, newCapacity(minimumCapacity) << coder);
}
}
原来, ensureCapacityInternal
用来判断拼接后的字符数组长度是否超过当前值,如果超过,进行扩容并复制。然后将拼接的字符串复制到目标数组 value 中。
2. concat方法的源码:
public String concat(String str) {
int olen = str.length();
if (olen == 0) {
return this;
}
if (coder() == str.coder()) {
byte[] val = this.value;
byte[] oval = str.value;
int len = val.length + oval.length;
byte[] buf = Arrays.copyOf(val, len);
System.arraycopy(oval, 0, buf, val.length, oval.length);
return new String(buf, coder);
}
int len = length();
byte[] buf = StringUTF16.newBytesFor(len + olen);
getBytes(buf, 0, UTF16);
str.getBytes(buf, len, UTF16);
return new String(buf, UTF16);
}
1)获取待拼接的字符串长度
int olen = str.length();
如果待拼接的字符串的长度为 0,那么直接返回拼接前的字符串
if (olen == 0) {
return this;
}
2)如果两者的编码相同,直接通过System.arraycopy进行拷贝并返回新的 String 对象
if (coder() == str.coder()) {
byte[] val = this.value;
byte[] oval = str.value;
int len = val.length + oval.length;
byte[] buf = Arrays.copyOf(val, len);
System.arraycopy(oval, 0, buf, val.length, oval.length);
return new String(buf, coder);
}
3) 如果编码不同,则使用 UTF16 编码分别将二者的值拷贝到字节数组上,并返回新的 String 对象
int len = length();
byte[] buf = StringUTF16.newBytesFor(len + olen);
getBytes(buf, 0, UTF16);
str.getBytes(buf, len, UTF16);
return new String(buf, UTF16);
通过源码分析,我们可以得出以下结论:
1)如果拼接的字符串是 null,concat 时候就会抛出 NullPointerException,“+”号操作符会当做是“null”字符串来处理。
2)如果拼接的字符串是一个空字符串 "",那么 concat 的效率要更高一点。毕竟不需要 new StringBuilder对象。
3)如果拼接的字符串非常多,concat 的效率就会下降,因为创建的字符串对象越多,开销就越大。
网友评论