字符串是由字符组成的,而字符由字节根据编码规则而来。这里我们的问题停留在字符以及字符串上,不涉及字符和字节的转换,想了解字符和字节转换以及编码规则请看字符编码(ASCII,Unicode和UTF-8,GBK)
平时开发中涉及到字符串拼接到底什么情况使用什么方法最合适呢?以及为什么选择这种方法呢?(或者说一下String,StringBuilder,StringBuffer异同等)相信大家面试的时候都遇到过。
下面先给出结论,再将结论的得出过程仔细分析一下。(jdk版本为1.8.0-51)
先给出拼接方式如下:
方法一: 使用“+”;
方法二: String.concat方法
方法三: StringBuilder.append方法
方法四: StringBuffer.append方法
下面是选择原则:
下面看看如何得出的这些结论。
String:是一个常量,一旦赋值就不可以修改了。(内部实现为字符数组,其为final类型)
StringBuilder:字符串变量(非线程安全)。(内部实现也是字符数组)
StringBuffer:字符串变量(Synchronized,即线程安全)。(内部实现也是字符数组)
String既然是final的,那么哪里来的拼接呢?其实,所有的所谓字符串拼接,都是重新生成了一个新的字符串。如下:
String s2 = "Hello";
String s3 = "Word";
String s4 = s2 + s3;
如果我们反编译会发现其内部在编译的时候其实是使用了StringBuilder。上面的拼接过程是:
String s4 = new StringBuilder().append(s2).append(s3).toString();
如果如下
String s1 = "Hello" + "Word";
其内部的实现和上面的原理就不同了,上面的这种拼接方法就相当于:
String s1 = "HelloWord";
因为在编译时候Hello和Word两个字符串被保存入常量池中,编译的时候就已经将两个字符串拼接为一个。关于类加载和编译相关的知识这里不展开说明。
下面是源码:
@FastNative
public native String concat(String str);
在jdk版本为1.8.0-51中已经成为了native方法,从注解@FastNative
来看它是将拼接过程放入了native层加快了拼接速度。
实例代码如下,将两个字符串进行拼接
StringBuilder sb = new StringBuilder();
String s = sb.append("Hello").append("Word").toString();
StringBuilder类的基类是AbstractStringBuilder,并且append也是使用的AbstractStringBuilder中的,下面我们来看AbstractStringBuilder。其中重要的几点:
下面是AbstractStringBuilder源码,只取相关部分
-------省略代码-------
/**
* 字符数组作为存储容器
*/
char[] value;
/**
* 数组中被使用的数量
*/
int count;
-------省略代码-------
//拼接
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
//确认是否要扩容
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
//扩容操作
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
StringBuilder源码,只取相关部分
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
@Override
public String toString() {
if (count == 0) {
return "";
}
return StringFactory.newStringFromChars(0, count, value);
}
StringBuffer也是继承的AbstractStringBuilder,并且相应的append,ensureCapacityInternal,newCapacity也是来自于其基类。不同的是StringBuffer中append方法是synchronized,于是保证了线程安全。
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
这里通过拼接代码来验证,如下
方法一: 使用“+”;
private void concatStr1() {
long t1 = System.currentTimeMillis();
String s = "hello";
for(int i=0;i<60000;i++){
String str = String.valueOf(i);
s+=str;
}
long t2 = System.currentTimeMillis();
long dur = t2-t1;
Log.e("cost_time=",dur+"");
}
方法二: String.concat方法
private void concatStr2() {
long t1 = System.currentTimeMillis();
String s = "hello";
for(int i=0;i<60000;i++){
String str = String.valueOf(i);
s.concat(str);
}
long t2 = System.currentTimeMillis();
long dur = t2-t1;
Log.e("cost_time=",dur+"");
}
方法三: StringBuilder.append方法
private void concatStr3() {
long t1 = System.currentTimeMillis();
String s = "hello";
StringBuilder sb = new StringBuilder();
for(int i=0;i<60000;i++){
sb.append(i);
}
long t2 = System.currentTimeMillis();
long dur = t2-t1;
Log.e("cost_time=",dur+"");
}
方法四: StringBuffer.append方法
private void concatStr4() {
long t1 = System.currentTimeMillis();
String s = "hello";
StringBuffer sb = new StringBuffer();
for(int i=0;i<60000;i++){
sb.append(i);
}
long t2 = System.currentTimeMillis();
long dur = t2-t1;
Log.e("cost_time=",dur+"");
}
下面是依次的耗时时间
06-14 15:42:13.554 25934-26300/com.gong.io.demo E/cost_time=: 79443
06-14 16:04:48.623 31326-31505/com.gong.io.demo E/cost_time=: 37
06-14 15:47:45.329 27786-27911/com.gong.io.demo E/cost_time=: 5
06-14 16:06:26.622 31965-32103/com.gong.io.demo E/cost_time=: 6
从中我们了解到耗时排名如下:
StringBuilder < StringBuffer < concat < +
得出结论: