問題:
理解 Java的字串,String、StringBuffer、StringBuilder 有什麼區別?
知識點
- 字串設計和實現考量 String是Immutable(執行緒安全、字串常量池複用)。Immutable物件在拷貝時候不需要額外複製資料。至於為什麼imumutable,原始碼如下:
//關鍵點 final
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
// The associated character storage is managed by the runtime. We only
// keep track of the length here.
//關鍵點final private
// private final char value[];
private final int count;
複製程式碼
- StringBuffer、StringBuilder底層都是利用可修改的陣列(JDK 9之後是byte)陣列,都繼承了AbstractStringBuilder,裡面包含了基本操作。區別StringBuffer增加了synchronized。相關原始碼如下:
//AbstractStringBuilder 沒有加final 也沒private修飾
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
private static final int MAX_ARRAY_SIZE = 2147483639;
AbstractStringBuilder() {
}
複製程式碼
//StringBuilder擷取部分原始碼
static final long serialVersionUID = 4383685877147921099L;
public StringBuilder() {
super(16);
}
public StringBuilder(int var1) {
super(var1);
}
public StringBuilder(String var1) {
super(var1.length() + 16);
this.append(var1);
}
public StringBuilder(CharSequence var1) {
this(var1.length() + 16);
this.append(var1);
}
public StringBuilder append(Object var1) {
return this.append(String.valueOf(var1));
}
public StringBuilder append(String var1) {
super.append(var1);
return this;
}
public StringBuilder append(StringBuffer var1) {
super.append(var1);
return this;
}
public StringBuilder append(CharSequence var1) {
super.append(var1);
return this;
}
public StringBuilder append(CharSequence var1, int var2, int var3) {
super.append(var1, var2, var3);
return this;
}
複製程式碼
//StringBuffer部分原始碼 多了synchronized
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
public synchronized int length() {
return count;
}
public synchronized int capacity() {
return value.length;
}
public synchronized void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > value.length) {
expandCapacity(minimumCapacity);
}
}
複製程式碼
- 字串快取 String在Java 6 以後提供了intern()方法,目的是提示JVM把相應字串快取起來,以便重複使用。
注意:Java 6這種歷史版本,並不推薦大量使用intern(),因為快取的字串是存在“永久代”中,這個空間比較有限。也基本不會被FULLGC之外的垃圾收集照顧到。所以,使用不當,容易OOM。後續版本中,被放到堆中,設定永久代在Java 8中被後設資料區替代了。
intern()感興趣可以參考文章:Java提高篇——理解String 及 String.intern() 在實際中的應用
- String自身也有相關優化 有興趣可以自己檢視相關文章,主要是char陣列換成了byte陣列加上一個標誌編碼所謂的coder。
回答問題:
String的建立機理
由於String在Java世界中使用過於頻繁,Java為了避免在一個系統中使用大量的Java物件,引入了字串常量池的概念。其執行機制是:建立一個字串的時候,首先檢查池中是否有相等的字串物件,如果有就不需要建立,直接從池中找到物件引用,如果沒有的話,新建字串物件,返回物件引用。但是,通過new方法建立的不會檢查常量池是否存在,而是直接在堆中或者棧中建立一個新的物件,也不會把物件放入池中。上面所說的只適用於直接給String引用賦值的情況。
注意:String是immutable Strng提供了inter()方法可以將Strng物件新增到池中,並且返回該物件的引用。(如果由equals()確定池中有該字串,那就直接返回)。
當兩個String物件擁有相等的值的時候,他們只引用字串中同一個拷貝。當同一個字串大量出現的時候,可以大量節省記憶體空間。
StringBuffer/StringBuilder
StringBuffer/StringBuilder相同點:
- String/StringBuilder相對於String的話,他們值是可以改變的,並且值改變後,他們引用不會變。他們在構造的時候使用一個預設的陣列,在後續加入資料後超過預設大小後會建立更大的陣列,並且將原來陣列的內容複製過來,再丟棄舊的陣列。因此,專案開發的時候,對於較大物件的擴容會效能,因此,能預估大小,最好不過。
StringBuffer/StringBuilder不同點:
- 另外StringBuffer是執行緒安全(方法定義前面使用了synchronize),StringBuilder不是。StringBuffer效能要低於StringBuilder。
應用場景:
String 適用於常量宣告。如:
public static final int DEVICE_NOT_AVAILABLE_ERROR_CODE = 390004;// 裝置未啟用或者被鎖住
複製程式碼
StringBuffer,適用於頻繁進行字串運算(如拼接、替換、刪除等),並且執行在多執行緒環境下,建議使用,比如Http引數解析和封裝。
/**
* 獲取POST請求的base url
*
* @param requestHolder
* @return
* @throws AlipayApiException
*/
private String getRequestUrl(RequestParametersHolder requestHolder) throws AlipayApiException {
StringBuffer urlSb = new StringBuffer(serverUrl);
try {
String sysMustQuery = WebUtils.buildQuery(requestHolder.getProtocalMustParams(),
charset);
String sysOptQuery = WebUtils.buildQuery(requestHolder.getProtocalOptParams(), charset);
urlSb.append("?");
urlSb.append(sysMustQuery);
if (sysOptQuery != null & sysOptQuery.length() > 0) {
urlSb.append("&");
urlSb.append(sysOptQuery);
}
} catch (IOException e) {
throw new AlipayApiException(e);
}
return urlSb.toString();
}
複製程式碼
StringBuilder,適用於頻繁進行字串運算(如拼接、替換、刪除等),並且執行在單執行緒環境下,建議使用,比如Json的封裝。
/**
* Extracts absolute path form a given URI. E.g., passing
* <code>http://google.com:80/execute?query=cat#top</code>
* will result in <code>/execute?query=cat#top</code>.
*
* @param uri URI which absolute path has to be extracted,
* @return the absolute path of the URI,
*/
private String getAbsolutePathFromAbsoluteURI(URI uri) {
String rawPath = uri.getRawPath();
String rawQuery = uri.getRawQuery();
String rawFragment = uri.getRawFragment();
StringBuilder absolutePath = new StringBuilder();
if (rawPath != null) {
absolutePath.append(rawPath);
} else {
absolutePath.append("/");
}
if (rawQuery != null) {
absolutePath.append("?").append(rawQuery);
}
if (rawFragment != null) {
absolutePath.append("#").append(rawFragment);
}
return absolutePath.toString();
}
複製程式碼
參考:
- Java提高篇——理解String 及 String.intern() 在實際中的應用
- String、StringBuffer、StringBuilder部分原始碼
- 自己開發Demo中部分程式碼
- 極客時間APP核心技術第五講|String、StringBuffer、StringBuilder有什麼區別?
宣告:此為原創,轉載請聯絡作者
作者:微信公眾號新增公眾號-遛狗的程式設計師 ,或者可以掃描以下二維碼關注相關技術文章。
當然喜愛技術,樂於分享的你也可以可以新增作者微訊號: