以JDK1.8源码为例
一、源码
String:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
……
}
StringBuilder:
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
……
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
}
StringBuffer:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
……
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
}
从源码中我们能看到:
1、三者都使用了final修饰,不能被继承;
2、三者都实现了java.io.Serializable接口,均可序列化和反序列化;
3、三者均实现同一接口CharSequence;
4、StringBuffer和StringBuilder都继承自同一个抽象类AbstractStringBuilder;
5、StringBuffer大部分方法前使用了synchronized关键字,而StringBuilder并没有使用。
二、String
1、String是不可变对象
首先,String是类,并不属于基本数据类型,虽然在使用中,有些地方和基本数据类型相似,例如:
String str="a";
int i=0;
String可以像基本数据类型一样赋值。但是它本质上是类,例如:
String s= new String("a");
其次,字符串一旦创建,对象永远无法改变,但字符串引用可以重新赋值,例如:
String str = "a";
str = str + "b";
代码中字符串"a"一旦创建用法无法改变,但是字符串引用str可以重新赋值为str+"b",也就是字符串"ab",事实上,在这个过程中总共创建了三个字符串对象,分别是:"a","b","ab"。
2、String常量池
java为提高字符串使用性能,静态字符串(字面量/常量/常量连接的结果)在字符串常量池中创建,并尽量使用同一个对象,重用静态字符串,对重复出现的字符串直接量,JVM会首先在常量池中查找,如果存在即返回该对象。例如:
//在字符串常量池中创建了"hello"对象,然后str1引用该对象
String str1="hello";
//不会创建新的"hello"对象,直接使用常量池中已有的"hello"对象
String str2="hello";
//字符串连接结果也是"hello",不会再创建"hello",直接使用常量池中"hello"
//但是注意的是,会创建"he"和"llo"对象
String str3="he"+"llo";
//输出true
System.out.println(str1==str2);
//输出true
System.out.println(str2==str3);
结果输出结果均为true,也就是str1、str2和str3都是相等的。
3、String内存编码及长度
Strign在内存中采用Unicode编码,任何一个字符(无论是中文还是英文)都算一个字符长度,占用两个字节。例如:
String str1="he";
String str2="你好";
//输出2
System.out.println(str1.length());
//输出2
System.out.println(str2.length());
三、StringBuilder和StringBuffer
1、StringBuilder和StringBuffer是可变对象
StringBuilder builder = new StringBuilder("a");
//输出a
System.out.println(builder);
builder.append("b");
//输出ab
System.out.println(builder);
2、StringBuilder的方法返回值
StringBuilder的很多方法的返回值也是StringBuilder,所以可以对一些操作连用,例如:
StringBuilder builder = new StringBuilder();
builder.append("a").append(1).append('c').deleteCharAt(2);
//输出a1
System.out.println(builder);
如果对字符串有大量的计算,应当选择StringBuilder,String在字符串运算中速度较慢。
3、StringBuffer与StringBuilder
两者基本相同,主要在线程操作中不同。StringBuffer因为使用了synchronized关键字,是线程安全的,而StringBuilder未使用synchronized关键字,是非线程安全的。例如:
package com.leboop;
public class StringTest {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder();
StringBuffer buffer = new StringBuffer();
//循环创建1000个线程
for(int i=0;i<1000;i++){
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
//每个线程都要追加100个1
for(int j=0;j<100;j++){
builder.append("1");
buffer.append("1");
System.out.println(builder.length()+"-"+buffer.length());
}
}
}).start();
}
}
}
输出结果
99988-99992
99989-99993
99990-99994
99991-99995
99992-99996
99993-99997
99994-99998
99995-99999
99996-100000
正确结果应该是100000。显然StringBuilder非线程安全,如果要深入理解需要理解并发编程和jvm内存模型。所以在实际应用中,少量的单线程字符串操作使用String,大量的单线程字符串操作使用StringBuilder,多线程字符串操作使用StringBuffer。