深入源碼分析StringBuffer和StringBuilder
衆所周知,StringBuffer是線程安全,StringBuilder線程不安全,是以StringBuilder性能略高,那還有沒有其他細節上的特性呢?讓我們從源碼分析
StringBuffer和StringBuilder都繼承了AbstractStringBuilder類
AbstractStringBuilder類關鍵源碼
- 抽象類,是Stringbuilder和StringBuffer的父類,有兩個主要的屬性:
- 字元數組value,用來存放字元串
- int類型的count值,用于
,并且每次增删改都會更新該count值計算存儲使用的字元數量
- capacity方法才是傳回字元數組的總長度,value.length;
- 該類中的大部分方法被其子類StringBuffer和StringBuilder所調用,其中的擴容機制是關鍵
abstract class AbstractStringBuilder implements Appendable, CharSequence {
//用來存字元串
char[] value;
//用來計算存儲使用的字元數量
int count;
public int length() {
//注意,這裡的length方法傳回的是count的值,而不是value.length,
//差別于String類中的length方法
return count;
}
//capacity()才是傳回字元數組的總長度
public int capacity() {
return value.length;
}
ensureCapacity擴容機制
//擴容機制 確定容量
public void ensureCapacity(int minimumCapacity) {
// 最小容量肯定大于0,嚴謹性判斷
if (minimumCapacity > 0)
// 一層一層環環相扣的封裝
ensureCapacityInternal(minimumCapacity);
}
private void ensureCapacityInternal(int minimumCapacity) {
//如果最小容量大于内置的數組長度就必須要擴容了
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
//開始擴容
void expandCapacity(int minimumCapacity) {
//自動擴容機制,每次擴容(value.length+1)*2
int newCapacity = value.length * 2 + 2;
//如果擴容後的容量還是小于傳入的參數,就直接将傳入的參數設為容量值
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
//如果傳入的參數小于0,則直接把容量設定到Integer的最大值 2^31-1
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
//還是使用了Arrays.copyOf函數直接傳回一個新容量的數組
value = Arrays.copyOf(value, newCapacity);
}
//用于保留value的值,保留的長度為count的值,隻有當count的值小于value.length時起作用
public void trimToSize() {
//當count小于value.length時,将value多餘長度的值删除,
//此時value.length的長度等于count
if (count < value.length) {
value = Arrays.copyOf(value, count);
}
}
// 設定長度length
public void setLength(int newLength) {
if (newLength < 0)
throw new StringIndexOutOfBoundsException(newLength);
//當傳入的值大于等于0時,進行擴容
ensureCapacityInternal(newLength);
//當傳入值大于字元統計量
if (count < newLength) {
//為新擴容的數組中的新元素填充'\0',同樣也是結束符 從count位置到newLength位置
Arrays.fill(value, count, newLength, '\0');
}
//最後設定新的字元長度量
count = newLength;
}
//append方法使用到的一個方法,用以添加一個字元串數組
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
{
if (srcBegin < 0)
throw new StringIndexOutOfBoundsException(srcBegin);
if ((srcEnd < 0) || (srcEnd > count))
throw new StringIndexOutOfBoundsException(srcEnd);
if (srcBegin > srcEnd)
throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
//用于添加字元串,将value的值添加到dst[]中
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
append方法
//append方法的重載
//在對象後拼接字元串或其他對象,效率相比String較高,
//因其在拼接時并沒有建立新的對象,不會造成大量對象的堆積,性能提升相當明顯
public AbstractStringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
public AbstractStringBuilder append(String str) {
if (str == null)
//若傳入的字元串為null,則預設添加“null”這個字元串
return appendNull();
int len = str.length();
//注意擴容,直接是計算字元量的長度,根據長度就擴容到這麼多
ensureCapacityInternal(count + len);
//用getChars方法去添加字元串數組
str.getChars(0, len, value, count);
//更新count字元量值,每次添加就會加上新加入的字元串長度值
count += len;
return this;
}
//拼接StringBuffer對象,方法同理
public AbstractStringBuilder append(StringBuffer sb) {
if (sb == null)
return appendNull();
int len = sb.length();
ensureCapacityInternal(count + len);
sb.getChars(0, len, value, count);
count += len;
return this;
}
//同樣也可以拼接AbstractStringBuilder對象,也可以是其實作類,例如StringBuilder
AbstractStringBuilder append(AbstractStringBuilder asb) {
if (asb == null)
return appendNull();
int len = asb.length();
ensureCapacityInternal(count + len);
asb.getChars(0, len, value, count);
count += len;
return this;
}
//對象為null時,就添加個“null”字元串
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;
}
//添加int類型的值
public AbstractStringBuilder append(int i) {
//細節,當int值是最小值時,特殊處理
if (i == Integer.MIN_VALUE) {
append("-2147483648");
return this;
}
//細節,判斷Integer的位數,負數有負号,要多加一位
int appendedLength = (i < 0) ? Integer.stringSize(-i) + 1
: Integer.stringSize(i);
//根據新傳入的int值,确定總體字元串長度值,根據此時長度值進行擴容
int spaceNeeded = count + appendedLength;
//擴容
ensureCapacityInternal(spaceNeeded);
//擴容之後就是添加值了,int值用Integer的靜态方法
Integer.getChars(i, spaceNeeded, value);
//更新總體的字元數組長度值
count = spaceNeeded;
return this;
}
//添加long類型的值,原理和int類似
public AbstractStringBuilder append(long l) {
if (l == Long.MIN_VALUE) {
append("-9223372036854775808");
return this;
}
int appendedLength = (l < 0) ? Long.stringSize(-l) + 1
: Long.stringSize(l);
int spaceNeeded = count + appendedLength;
ensureCapacityInternal(spaceNeeded);
Long.getChars(l, spaceNeeded, value);
count = spaceNeeded;
return this;
}
//浮點類型,直接調用對應的包裝類中的方法
public AbstractStringBuilder append(float f) {
FloatingDecimal.appendTo(f,this);
return this;
}
public AbstractStringBuilder append(double d) {
FloatingDecimal.appendTo(d,this);
return this;
}
//添加Boolean類型 簡單粗暴
public AbstractStringBuilder append(boolean b) {
if (b) {
ensureCapacityInternal(count + 4);
value[count++] = 't';
value[count++] = 'r';
value[count++] = 'u';
value[count++] = 'e';
} else {
ensureCapacityInternal(count + 5);
value[count++] = 'f';
value[count++] = 'a';
value[count++] = 'l';
value[count++] = 's';
value[count++] = 'e';
}
return this;
}
delete方法
//删除一部分的字元,傳入要删除段的開始下标和終止下标
public AbstractStringBuilder delete(int start, int end) {
//驗證參數的有效性
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
//結束下标大于count時,将count設為結束下标
end = count;
if (start > end)
//開始下标就大于結束下标
throw new StringIndexOutOfBoundsException();
//要删除的長度值
int len = end - start;
if (len > 0) {
//System的靜态方法來實作數組之間的複制。
//src:源數組;
//srcPos:源數組要複制的起始位置;
//dest:目的數組;
//destPos:目的數組放置的起始位置;
//length:複制的長度。注意:src and dest都必須是同類型或者可以進行轉換類型的數組.
//System.arraycopy函數可以實作自己到自己複制
//将中間要删除段省略,不進行複制
System.arraycopy(value, start+len, value, start, count-end);
//更新count大小
count -= len;
}
return this;
}
insert方法
//在對象中間插入字元串數組
public AbstractStringBuilder insert(int dstOffset, CharSequence s) {
if (s == null)
s = "null";
//明确了是String類型,會發生強制轉換
if (s instanceof String)
return this.insert(dstOffset, (String)s);
return this.insert(dstOffset, s, 0, s.length());
}
//傳入的是插入開始下标,以及要插入的字元串
public AbstractStringBuilder insert(int offset, String str) {
if ((offset < 0) || (offset > length()))
throw new StringIndexOutOfBoundsException(offset);
if (str == null)
str = "null";
int len = str.length();
ensureCapacityInternal(count + len);//先擴容
//再複制數組,注意,此時隻是在value中建立起用于存放插入值的空位
System.arraycopy(value, offset, value, offset + len, count - offset);
//向空位中插入str
str.getChars(value, offset);
//更新count值
count += len;
return this;
}
reverse方法
//反轉字元串
public AbstractStringBuilder reverse() {
boolean hasSurrogates = false;
int n = count - 1;
//采用從中間向兩端周遊,對換對稱位置上的字元
for (int j = (n-1) >> 1; j >= 0; j--) {
int k = n - j;
char cj = value[j];//兩個暫存變量
char ck = value[k];
value[j] = ck;//直接對應位置交換
value[k] = cj;
//驗證每個字元的編碼是否在範圍内
if (Character.isSurrogate(cj) ||
Character.isSurrogate(ck)) {
hasSurrogates = true;
}
}
if (hasSurrogates) {
//直接反轉後,如果兩字元順序錯了,就需要重新調整。
//考慮到存在增補字元,需要成對校驗,就是超出了字元的編碼範圍的話就會重新翻轉到原來那樣
reverseAllValidSurrogatePairs();
}
return this;
}
//reverse的依賴方法,重新調整字元順序
private void reverseAllValidSurrogatePairs() {
for (int i = 0; i < count - 1; i++) {
char c2 = value[i];
if (Character.isLowSurrogate(c2)) {
char c1 = value[i + 1];
if (Character.isHighSurrogate(c1)) {
value[i++] = c1;
value[i] = c2;
}
}
}
}
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);
//注意是傳回了一個新建立的字元串對象
return new String(value, start, end - start);
}
}
System.arraycopy
Arrays.copyOf()方法傳回的數組是新的數組對象,數組拷貝時調用的是本地方法 System.arraycopy() ,原數組對象仍是原數組對象,不變,該拷貝不會影響原來的數組。
System.arraycopy() 源碼如下:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
參數說明:
src:源對象
srcPos:源數組中的起始位置
dest:目标數組對象
destPos:目标資料中的起始位置
length:要拷貝的數組元素的數量
StringBuilder的關鍵源碼
- 線程不安全,修改的方法全部都為線程不安全,是犧牲了安全用以實作性能
- 由final修飾,不能被繼承,并且繼承了AbstractStringBuilder類
- 重寫了toString方法
- 實作了序列化接口,可序列化
- 預設初始化容量為capacity = 16 ,基本所有jdk中實作類涉及初始化容量的大小都為16,加上一點擴容機制
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
//對象序列化和反序列化需要的唯一辨別版本号
static final long serialVersionUID = 4383685877147921099L;
//預設構造方法
public StringBuilder() {
//使用父類的構造方法,預設初始化容量為capacity = 16
super(16);
}
//帶一個參數的構造方法,可以指定初始化容量
public StringBuilder(int capacity) {
super(capacity);
}
//帶一個參數構造方法,與前一個不同,這是指定一個String來初始化
public StringBuffer(String str) {
//注意,String初始化StringBuffer的時候,指定容量大小為String的長度加上16
super(str.length() + 16);
//追加到value中
append(str);
}
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
@Override
public String toString() {
// Create a copy, don't share the array
// 注意,此時是new了一個String對象,傳回了該String對象
return new String(value, 0, count);
}
//将對象序列化,寫入了count和value。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
s.writeInt(count);
s.writeObject(value);
}
//用于反序列化,将count和value屬性讀取出來
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
count = s.readInt();
value = (char[]) s.readObject();
}
}
StringBuffer的關鍵源碼
一個字元序列可變的字元串,這個StringBuffer提供了一系列的修改方法去改變字元串對象序列
- 線程安全,增删改操作方法都加了
鎖,加鎖系統開銷大,效率相對StringBuilder較低synchronized
- 注意:和Builder的差別有一個
,toStringCache的字元數組,最後一次修改後的緩存值(字元數組儲存),隻要修改了value,那麼就會重置,當然transient char[] toStringCache
的,不保證線程安全就不會涉及到該Cache的操作,這個Cache是建立線上程安全之上
該toStringCache會在使用toString方法時起到提高效率的作用
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
// 注意:此屬性Builder中沒有,意圖是最後一次修改後的緩存值(字元數組儲存),
// 隻要修改了value,那麼就會重置
// transient 用于指定哪個字段不被預設序列化
private transient char[] toStringCache;
static final long serialVersionUID = 3388685877147921107L;
public StringBuffer() {
super(16);
}
public StringBuffer(int capacity) {
super(capacity);
}
// 擷取字元串字元數量,加synchronized鎖
@Override
public synchronized int length() {
return count;
}
// 擷取容量,效率低--對象鎖
@Override
public synchronized int capacity() {
return value.length;
}
// 確定容量
@Override
public synchronized void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > value.length) {
// 當最小容量值(傳進來的參數值)大于value.length(這個其實就是容量),那麼就直接擴容
expandCapacity(minimumCapacity);
}
}
//擴容,和AbstractStringBuilder父類中方法一緻
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0)
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
@Override
public synchronized void trimToSize() {
//直接調用父類的方法,但線程安全
super.trimToSize();
}
//也是線程安全
@Override
public synchronized void setLength(int newLength) {
toStringCache = null;
//調用父類函數,擴充字元串容量到newLength,并且用空格填充
super.setLength(newLength);
}
//根據指定索引擷取字元,效率慢,加鎖
@Override
public synchronized char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}
//根據索引修改字元串中某個字元值
@Override
public synchronized void setCharAt(int index, char ch) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
//注意清除了緩存,隻要修改了value,此值就會置空
toStringCache = null;
value[index] = ch;
}
//差別在于加鎖了以及清除了緩存,隻要修改了value,此值就會置空
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
//不存在記憶體洩漏,實作了線程安全
@Override
public synchronized String substring(int start) {
return substring(start, count);
}
@Override
public synchronized String substring(int start, int end) {
return super.substring(start, end);
}
//有一套這樣的insert方法,分為同步與不同步方法
//隻有在參數為char類型時,才是同步方法
@Override
public StringBuffer insert(int offset, boolean b) {
//此方法不同步, 而且也沒有 toStringCache = null;
super.insert(offset, b);
return this;
}
@Override
public synchronized StringBuffer insert(int offset, char c) {
toStringCache = null;
super.insert(offset, c);
return this;
}
@Override
public synchronized String toString() {
//toStringCache是提高toString方法的效率,不用每次都是調用,做了一個緩存
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
//直接使用的toStringCache數組緩存,提高了效率
return new String(toStringCache, true);//傳回一個新的string對象過去
}
// 自定義序列化字段
//serialPersistentFields 用于指定哪些字段需要被預設序列化.如下:
private static final java.io.ObjectStreamField[] serialPersistentFields =
{
new java.io.ObjectStreamField("value", char[].class),
new java.io.ObjectStreamField("count", Integer.TYPE),
new java.io.ObjectStreamField("shared", Boolean.TYPE),
};
// 序列化大到ObjectOutputStream,寫入了count和value、shared
private synchronized void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
java.io.ObjectOutputStream.PutField fields = s.putFields();
fields.put("value", value);
fields.put("count", count);
fields.put("shared", false);//預設shared為false
s.writeFields();
}
//反序列化到對象,讀出count和value
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
java.io.ObjectInputStream.GetField fields = s.readFields();
value = (char[])fields.get("value", null);
count = fields.get("count", 0);
}
}
字元串系列類的一些問題
效率問題
效率:StringBuilder > StringBuffer > String
- StringBuffer和StringBuilder都有
機制,增删改操作時,傳回的是原來自身的對象,不會重新建立對象,不會産生很多對象垃圾自動擴容
- String有兩大缺點:
- 傳回對象使用大量new操作,傳回值均為建立的String對象,會在堆記憶體中産生很多垃圾;
- 雖然最終調用的是系統複制數組操作,但調用之前開銷非常大,隻能靠複制來解決拼接問題。
public static void main(String[] args) {
long start = System.currentTimeMillis();
String str = null;
for (int i = 0; i < 20000; i++) {
str = str + i + ",";
}
System.out.println("String耗時 "+ (System.currentTimeMillis() - start));
System.out.println("-------------------");
start = System.currentTimeMillis();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < 20000; i++) {
buffer.append(i + ",");//線程安全是以慢一點,但前提這裡是單線程
}
System.out.println("StringBuffer耗時 "+(System.currentTimeMillis() - start));
System.out.println("-------------------");
start = System.currentTimeMillis();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 20000; i++) {
builder.append(i + ",");//線程不安全,效率最高
}
System.out.println("StringBuilder耗時 "+(System.currentTimeMillis() - start));
}
/*
String耗時 2030
-------------------
StringBuffer耗時 5
-------------------
StringBuilder耗時 3
*
線程安全問題
- 增删改操作時,StringBuffer線程安全,StringBuilder線程不安全
使用總結
- 如果要操作少量的String資料可以使用用 String
- 不考慮線程安全問題,大量資料進行操作使用 StringBuilder
- 考慮線程安全問題,大量資料進行操作使用 StringBuffer