String-StringBuffer-StringBuilder的區別和原始碼分析
一,String,StringBuffer,StringBuilder三者之間的關係
三個類的關係:StringBuffer和StringBuilder都繼承自AbstractStringBuilder這個類,
而AbstractStringBuilder和String都繼承自Object這個類(Object是所有java類的超類)
可以通過如下的部分原始碼看到:
String:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { 。。。。。 }
StringBuffer:
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence{ 。。。 }
StringBuilder:
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence{ 。。。。。 }
二,String是不可變類,而StringBuffer, StringBuilder是可變類
我們檢視這三個類的原始碼,發現String類沒有append()、delete()、insert()這三個成員方法,而StringBuffer和StringBuilder都有這些方法,StringBuffer和StringBuilder中的這三個方法都是通過system類的arraycopy方法來實現的,即將原陣列複製到目標陣列
1.String類對字串的擷取操作
public String substring(int beginIndex) { if (beginIndex < 0) {throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } //當對原來的字串進行擷取的時候(beginIndex >0),返回的結果是新建的物件 return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); }
當我們對字串從第beginIndex(beginIndex >0) 個字元開始進行擷取時,返回的結果是重新new出來的物件。所以,在對String型別的字串進行大量“插入”和“刪除”操作時會產生大量的臨時變數。
2.StringBuffer與StringBuilder:
因為StringBuffer和StringBuilder都繼承自這個抽象類,即AbstractStringBuilder類是StringBuffer和StringBuilder的共同父類。AbstractStringBuilder類的關鍵程式碼片段如下:
abstract class AbstractStringBuilder implements Appendable, CharSequence { /** * The value is used for character storage. */ char[] value;//一個char型別的陣列,非final型別,這一點與String類不同 /** * This no-arg constructor is necessary for serialization of subclasses. */ AbstractStringBuilder() { } /** * Creates an AbstractStringBuilder of the specified capacity. */ AbstractStringBuilder(int capacity) { value = new char[capacity];//構建了長度為capacity大小的陣列 } //其他程式碼省略…… …… }
StringBuffer:
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence { /** * Constructs a string buffer with no characters in it and an * initial capacity of 16 characters. */ public StringBuffer() { super(16);//建立一個預設大小為16的char型陣列 } /** * Constructs a string buffer with no characters in it and * the specified initial capacity. * * @param capacity the initial capacity. * @exception NegativeArraySizeException if the {@code capacity} * argument is less than {@code 0}. */ public StringBuffer(int capacity) { super(capacity);//自定義建立大小為capacity的char型陣列 } //省略其他程式碼……
StringBuilder類的建構函式與StringBuffer類的建構函式實現方式相同,此處就不貼程式碼了。
下面來看看StringBuilder類的append方法和insert方法的程式碼,因StringBuilder和StringBuffer的方法實現基本上一致,不同的是StringBuffer類的方法前多了個synchronized關鍵字,
即StringBuffer是執行緒安全的。所以接下來我們就只分析StringBuilder類的程式碼了。StringBuilder類的append方法,insert方法都是Override 父類AbstractStringBuilder的方法,
所以我們直接來分析AbstractStringBuilder類的相關方法。
public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); //呼叫下面的ensureCapacityInternal方法 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) //呼叫下面的expandCapacity方法實現“擴容”特性 expandCapacity(minimumCapacity); } /** * This implements the expansion semantics of ensureCapacity with no * size check or synchronization. */ void expandCapacity(int minimumCapacity) { //“擴充套件”的陣列長度是按“擴充套件”前陣列長度的2倍再加上2 byte的規則來擴充套件 int newCapacity = value.length * 2 + 2; if (newCapacity - minimumCapacity < 0) newCapacity = minimumCapacity; if (newCapacity < 0) { if (minimumCapacity < 0) // overflow throw new OutOfMemoryError(); newCapacity = Integer.MAX_VALUE; } //將value變數指向Arrays返回的新的char[]物件,從而達到“擴容”的特性 value = Arrays.copyOf(value, newCapacity); }
從上述程式碼分析得出,StringBuilder和StringBuffer的append方法“擴容”特性本質上是通過呼叫Arrays類的copyOf方法來實現的。接下來我們順藤摸瓜,再分析下Arrays.copyOf(value, newCapacity)這個方法吧。程式碼如下:
public static char[] copyOf(char[] original, int newLength) { //建立長度為newLength的char陣列,也就是“擴容”後的char 陣列,並作為返回值 char[] copy = new char[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy;//返回“擴容”後的陣列變數 }
其中,insert方法也是呼叫了expandCapacity方法來實現“擴容”特性的,此處就不在贅述了。
接下來,分析下delete(int start, int end)方法,程式碼如下:
public AbstractStringBuilder delete(int start, int end) { if (start < 0) throw new StringIndexOutOfBoundsException(start); if (end > count) end = count; if (start > end) throw new StringIndexOutOfBoundsException(); int len = end - start; if (len > 0) { //呼叫native方法arraycopy對value陣列進行復制操作,然後重新賦值count變數達到“刪除”特性 System.arraycopy(value, start+len, value, start, count-end); count -= len; } return this; }
從原始碼可以看出delete方法的“刪除”特性是呼叫native方法arraycopy對value陣列進行復制操作,然後重新賦值count變數實現的
最後,來看下substring方法,原始碼如下 :
public String substring(int start, int end) { if (start < 0) throw new StringIndexOutOfBoundsException(start); if (end > count) throw new StringIndexOutOfBoundsException(end); if (start > end) throw new StringIndexOutOfBoundsException(end - start); //根據start,end引數建立String物件並返回 return new String(value, start, end - start); }
三,執行速度:
總體上:String小於StringBuffer小於StringBuilder
原因:String是不可變的,為字串常量,每次對 String 型別進行改變的時候其實都等同於生成了一個新的 String 物件,然後將指標指向新的 String 物件。這就會對程式執行產生很大的影響,因為當記憶體中的無引用物件多了以後,JVM的GC程序就會進行垃圾回收,這個過程會耗費很長一段時間,因此經常改變內容的字串最好不要用 String類的物件。而如果是使用 StringBuffer 類則結果就不一樣了,每次結果都會對 StringBuffer 物件本身進行操作,而不是生成新的物件,再改變物件引用。所以在一般情況下我們推薦使用 StringBuffer ,特別是字串物件經常改變的情況下。
注意:但是在某些特殊情況下,String物件的字串拼接其實是被JVM解釋成了StringBuffer物件的拼接,所以這時候String物件的速度並不比StringBuffer物件慢。如:
String m = "I"+"am"+"boy"; StringBuffer n = new StringBuffer("I").append("am").append("boy");
但是如果要拼接的字串來自於不同的String物件的話,那結果就不一樣了(常用),StringBuffer要比String快的多,如:
String p = "China is"; String q = "very good"; String t = p + q;
在執行速度方面StringBuffer<StringBuilder,這是因為StringBuffer由於執行緒安全的特性,常常應用於多執行緒的程式中,為了保證多執行緒同步一些執行緒就會遇到阻塞的情況,這就使得StringBuffer的執行時間增加,從而使得執行速度減慢;而StringBuilder通常不會出現多執行緒的情況,所以執行時就不會被阻塞,執行速度也自然就比StringBuffer快了。
四,執行緒安全與不安全
StringBuffer是執行緒安全的,StringBuilder是非執行緒安全的
部分原始碼:StringBuffer:
public synchronized String substring(int start) { return substring(start, count); } /** * @throws IndexOutOfBoundsException {@inheritDoc} * @since 1.4 */ @Override public synchronized CharSequence subSequence(int start, int end) { return super.substring(start, end); } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} * @since 1.2 */ @Override public synchronized String substring(int start, int end) { return super.substring(start, end); } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} * @since 1.2 */ @Override public synchronized StringBuffer insert(int index, char[] str, int offset, int len) { toStringCache = null; super.insert(index, str, offset, len); return this; } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override public synchronized StringBuffer insert(int offset, Object obj) { toStringCache = null; super.insert(offset, String.valueOf(obj)); return this; }
StringBuilder:
@Override public StringBuilder append(long lng) { super.append(lng); return this; } @Override public StringBuilder append(float f) { super.append(f); return this; } @Override public StringBuilder append(double d) { super.append(d); return this; } /** * @since 1.5 */ @Override public StringBuilder appendCodePoint(int codePoint) { super.appendCodePoint(codePoint); return this; }
我們可以發現StringBuffer類中的大部分成員方法都被synchronized關鍵字修飾,而StringBuilder類沒有出現synchronized關鍵字;至於StringBuffer類中那些沒有用synchronized修飾的成員方法,如insert()、indexOf()等,通過原始碼上的註釋可以知道,它們是呼叫StringBuffer類的其他方法來實現同步的。注意:toString()方法也是被synchronized關鍵字修飾的
五,synchronized關鍵字解析:
一>、修飾一個程式碼塊,被修飾的程式碼塊稱為同步語句塊,其作用的範圍是大括號{}括起來的程式碼,作用的物件是呼叫這個程式碼塊的物件;
- 當兩個併發執行緒訪問同一個物件中的synchronized(this){}同步程式碼塊時,同一時間內只能有一個執行緒得到執行。另一個執行緒必須等待當前執行緒執行完這個程式碼塊以後才能執行該程式碼塊;
- 當一個執行緒訪問物件中的一個synchronized(this){}同步程式碼塊時,另一個執行緒仍然可以訪問該物件中的非synchronized(this){}同步程式碼塊;
- 當一個執行緒訪問物件中的一個synchronized(this){}同步程式碼塊時,其他執行緒對物件中所有其它synchronized(this){}同步程式碼塊的訪問將被阻塞。
二>、修飾一個方法,被修飾的方法稱為同步方法,其作用的範圍是整個方法,作用的物件是呼叫這個方法的物件;
三>、修飾一個靜態的方法,其作用的範圍是整個靜態方法,作用的物件是這個類的所有物件;
- 因為靜態方法(或變數)是屬於其所屬類的,而不是屬於該類的物件的,所以synchronized關鍵字修飾的靜態方法鎖定的是這個類的所有物件,即所有物件都是同一把鎖。
六, String, StringBuffer, StringBuilder都能夠用final關鍵字修飾,此處不再過多解釋。
但需要注意的是:
- final修飾的類不能被繼承;
- final修飾的方法不能被繼承類重寫;
- final修飾的變數為常量,不能被改變。
七,總結:
1、String型別的字串物件是不可變的,一旦String物件建立後,包含在這個物件中的字元系列是不可以改變的,直到這個物件被銷燬。
2、StringBuilder和StringBuffer型別的字串是可變的,不同的是StringBuffer型別的是執行緒安全的,而StringBuilder不是執行緒安全的
3、如果是多執行緒環境下涉及到共享變數的插入和刪除操作,StringBuffer則是首選。如果是非多執行緒操作並且有大量的字串拼接,插入,刪除操作則StringBuilder是首選。畢竟String類是通過建立臨時變數來實現字串拼接的,耗記憶體還效率不高,怎麼說StringBuilder是通過JNI方式實現終極操作的。
4、StringBuilder和StringBuffer的“可變”特性總結如下:
(1)append,insert,delete方法最根本上都是呼叫System.arraycopy()這個方法來達到目的
(2)substring(int, int)方法是通過重新new String(value, start, end - start)的方式來達到目的。因此,在執行substring操作時,StringBuilder和String基本上沒什麼區別。
本文參考部落格:https://www.cnblogs.com/Wilange/p/7570633.html
https://blog.csdn.net/hj7jay/article/details/52770174