String、StringBuilder和StringBuffer詳解
以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。