今天在腾讯面视的时候有被问到java中有哪几种拼接字符串的方法。
回顾起来,这应该是一个很重要的点,可惜自己没答好。
回来后仔细分析String内里面的源码,对于拼接字符串进行一个分析。
”+“ 方法源码中描述如下
The Java language provides special support
for the string concatenation operator ( + ),
and for conversion of other objects to strings.
String concatenation is implemented through
the StringBuilder(or StringBuffer) class and its append method.
意思就是:String本身是不变的对象,但是string的+号操作符是通过StringBuilder或StringBuffer(可以通过反汇编class文件,看到使用的StringBuilder来实现的。)
“+” 是在java内部是对+进行了重载,在处理String的过程中要创建一个StringBuffer对象,用StringBuffer对象的append方法对字符串进行连接,最后调用toString方法返回String字符串。
所以 “+” 方法可以随意使用,比如String+int, String+Double····但是效率较低。
看看String类里面concat源码。
private final char value[];
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);
}
1.首先传入一个字符串str,然后得到str的长度,命名为otherLen。
2.判断oherLen是否为0。如果长度为0,那么返回原字符串。(一个字符串加上一个空字符串,得到还是原字符串)
3.取原字符串长度,命名为len。(注意这里取是从value数组里面取,value数组里实际上就是存储的原字符串)
4.然后用Arrays.copyOf函数,复制原数组,并且把数组长度从len拓展到len+otherLen。
5.用getChars函数,把str复制进入buf数组里,从下标为len的地方开始复制。
6.用buf新构建一个String对象,并且返回。
ps:注意,如果传入的str为空,那么就不会构建新String对象,而是直接返回原对象。并且concat只能对字符串使用。因为已经确认是字符串型,所以没有类型转换,效率相对 “+” 高许多。
join方法首先需要导入一个包:
org.apache.commons.lang
导入方法自行Google。
jar包下载
http://maven.outofmemory.cn/org.apache.commons/commons-lang3/3.3.2/
所以严格来说这个不是String类里面自带的一个方法。但是也值得一提。(JDK8里面String类也有了join方法,效果类似,传参不同而已)
在StringUtils.java里面join的源码如下
public static String join(final Iterable> iterable, final String separator) {
if (iterable == null) {
return null;
}
return join(iterable.iterator(), separator);
}
里面用迭代器方法遍历整个传进来的数组或集合,然后用第二个参数separator来把数组或集合连接起来。(关于迭代器方面,了解的不深,不过根据代码运行结果可以逆推出结论)。
那么join方法具体思路大概就清楚了:
1.申明一个
2.用list.add()方法,把要连接的字符串存入list里面。
3.用StringUtils.join(list,""),方法把list里面的字符串连接起来。(因为第二个参数是“”,所以list里面数据就直接相连,相当于字符串就连接起来了)
这个方法速度快在list的add方法比String类里面的+或者concat都迅速。
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
调用父类AbstractStringBuilder里面的append方法。
父类里面方法如下:
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;
}
如果传进来的字符str为null的话,调用appendNull方法(见下面代码),那么直接在后面加上null这个字符串。(比如已经有"abc"了,有一个字符串a=null,调用append(a)方法,就会进入appendNull,最后得到的结果是“abcnull”,而不是"abc"。但是如果加入进来的a="",那么结果就是"abc"。)
如果不是null的话,那么就开扩容量,又用getChars方法,把str给放入value里面(value里面是原字符串对应的数组)去,并且把对于长度修正。
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}
从上面就可以发现,StringBuffer其实也是调用了AbstractStringBulider里面的方法。
而StringBuilder也是如此,只不过比StringBuffer少了一个toStringCache以及一些方法的synchronized关键字。这些关键字主要是用来确保线程的安全问题。所以StringBuffer比StringBuilder虽然看起来一样,但是牺牲了少量的性能,换取了一定的安全性。
下面是StringBuilder里面append源码:
public StringBuilder append(String str) {
super.append(str);
return this;
}
里面调用的super.append是一模一样的跳入AbstractStringBuilder里面的append方法。
import java.sql.Time;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
public class test {
public static void main(String agv[]) throws InterruptedException {
String strX="x";
String strSum="";
//方法1,+法
long start = System.currentTimeMillis();
for(int i=0;i<120000;i++){
strSum+=strX;
}
long end = System.currentTimeMillis();
System.out.println(end-start);
System.out.println(strSum);
// System.out.println(strSum.length());
// Thread.sleep(100);
strSum="";
//方法2,concat
start = System.currentTimeMillis();
for(int i=0;i<120000;i++){
strSum=strSum.concat(strX);
}
end = System.currentTimeMillis();
System.out.println(end-start);
System.out.println(strSum);
// System.out.println(strSum.length());
// Thread.sleep(100);
strSum="";
//方法3,join
List list=new ArrayList();
start=System.currentTimeMillis();
for(int i=0;i<120000;i++){
list.add(strX);
}
strSum=StringUtils.join(list, "");
end = System.currentTimeMillis();
System.out.println(end-start);
System.out.println(strSum);//输出list看是否正确
// System.out.println(list.size());
// Thread.sleep(100);
strSum="";
//方法4,StringBuffer
StringBuffer sbr=new StringBuffer();
start=System.currentTimeMillis();
for(int i=0;i<120000;i++){
sbr.append(strX);
}
strSum=sbr.toString();
end=System.currentTimeMillis();
System.out.println(end-start);
System.out.println(strSum);
//System.out.println(strSum.length());
//Thread.sleep(100);
//方法5,StringBuilder
StringBuilder sbd=new StringBuilder();
start=System.currentTimeMillis();
for(int i=0;i<120000;i++){
sbd.append(strX);
}
strSum=sbd.toString();
end=System.currentTimeMillis();
System.out.println(end-start);
System.out.println(strSum);
//System.out.println(strSum.length());
StringBuffer testNull=new StringBuffer();//用于测试null的
String a="abc";
testNull.append(a);
a=testNull.toString();
System.out.println(a);
a= null;
testNull.append(a);
a=testNull.toString();
System.out.println(a);
}
}
但是需要注意的是,StringBuilder会有线程不安全问题(原因在上文),而StringBuufer不会。
所以在大量字符串处理时,尽可能的多使用StringBuffer或者join。对于少量的字符串处理,可以使用+或者concat。
并且,因为String类型是常量,而StringBuffer,StringBuilder类型是变量,所以JVM在对其处理时,String往往会多次被销毁,创建,这会占据大量的时间。而另外两种相对较少。
下面是放在Github的代码以及本人的Github,不定期的更新一些Java学习的代码以及有意思的面试题对应的代码。
https://github.com/2017faker/my_project.git
https://github.com/2017faker