三、String、StringBuffer 和 StringBuilder
String、StringBuffer 和 StringBuilder 都位於 java.lang 包下,都屬於字串操作類。String 使用了final類修飾,是不可變類;StringBuffer 是執行緒安全類,StringBuilder 是非執行緒安全類,巴拉巴拉。。。僅僅知道這些,再往深一點就不太清楚了,本篇博文基於原始碼探討一下這三個類的區別。
其實,它們三個都實現了Serializable 和 CharSequence 介面;String 同時實現了Comparable 介面用於比較;StringBuffer 和 StringBuilder 繼承自AbstractStringBuilder抽象類。依賴關係如圖所示。
一、String 解析
String是 final 修飾的不可變類,其內部只有四個非函式成員:
/** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static finallong serialVersionUID = -6849794470754667710L; /** * Class String is special cased within the Serialization Stream Protocol. * * A String instance is written into an ObjectOutputStream according to * <a href="{@docRoot}/../platform/serialization/spec/output.html"> * Object Serialization Specification, Section 6.2, "Stream Elements"</a>*/ private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
其中 value 是常量字串陣列,表示這個陣列引用不能再指向其他地址,在String類中都是在建構函式中初始化賦值;hash 用於快取當前字串的雜湊值(稍後會解釋為什麼需要單獨使用一個變數來儲存雜湊值)。
final 修飾的成員變數要麼定義的時候就初始化,要麼在建構函式中初始化,並且要在全部的建構函式都要初始化;已初始化後不能重新初始化或賦值,否則不能編譯通過。
1. String 的多個建構函式
public String() { this.value = "".value;} // 非必要,因為字串是不可變的 public String(String original) { // 以字串初始化時,只是將這兩個內部成員賦值 this.value = original.value; this.hash = original.hash; } public String(char value[]) { // 其實呼叫的是System.arraycopy底層方法將字元陣列複製一份,陣列引用再指向複製的字元陣列 this.value = Arrays.copyOf(value, value.length); } public String(byte bytes[]) { // 位元組陣列的建構函式 this(bytes, 0, bytes.length); } public String(StringBuffer buffer) { // StringBuffer的建構函式,使用的synchronized修飾,考慮到了執行緒安全,實際上是對StringBuffer內的字元陣列複製一份,並使value指向它 synchronized(buffer) { this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); } } public String(StringBuilder builder) { // 與StringBuffer同理,只是少了synchronized this.value = Arrays.copyOf(builder.getValue(), builder.length()); } // 其他的建構函式更多是字元陣列、整形陣列、位元組陣列位移擷取的操作,原理其實還是將需要的部分生成一份字元陣列,並使value指向它,
// 前面講過,value定義時沒有初始化,全部的建構函式中將value初始化,普通函式不能將value重新指向其他字元陣列,因此String是不可變的字串類,String也無法向外部提供可修改內部value字元陣列的方法
2. String 常用函式解析
(1)length()
public int length() { // 返回的是字元陣列的長度 return value.length; }
(2)isEmpty()
public boolean isEmpty() { // 字元陣列長度為0時,即為"".value.length return value.length == 0; }
(3)charAt(int index)
public char charAt(int index) { // 越界時會丟擲字串索引越界異常 if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; }
(4)getBytes()
public byte[] getBytes() { // 獲取字串的位元組陣列,有設定chartset的引數, return StringCoding.encode(value, 0, value.length); }
(5)equals(Object anObject)
public boolean equals(Object anObject) { // 重寫了Object的方法 if (this == anObject) { // 判斷引用是否與自己相等 return true; } if (anObject instanceof String) { // 判斷是否是String型別,因此如果是StringBuffer或者StringBuilder,即使字元陣列內容相同也返回的是false,如果相比較可以使用contentEquals String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { // 判斷長度是否相同 char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
(6)compareTo(String anotherString)
public int compareTo(String anotherString) { // 實現的是Comparable的方法,用於字串的比較,原理是字元陣列元素依次比較 int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } return len1 - len2; }
(7)hashCode()
public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { // 0代表hash沒有被計算出來,已有快取的hash值直接返回即可,快取hash省去了每次的計算 char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
這是重頭戲,String重寫了Object的 hashCode() 方法,從字元陣列的首位開始,當前hash值*32 + 當前字元轉變的int型數值,雜湊計算公式可以計為s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]。
二、AbstractStringBuilder 解析
在分析 StringBuffer 和 StringBuilder 之前,先分析一下它們的共同繼承類 AbstractStringBuilder。
在程式碼中可以看到,它的內部有兩個成員變數:
/** * The value is used for character storage. */ char[] value; // 字元陣列 注意!!這裡的value沒有使用final修飾,可以重新賦值 /** * The count is the number of characters used. */ int count; // value中真正已使用的長度,count<=value.length
定義了兩個建構函式,一個無引數;一個有引數,初始化了value的長度:
/** * 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]; }
同時,重寫了CharSequence的 length() 方法,返回的不是 value 陣列的實際長度,而是其已使用的長度,即 count 值。
@Override public int length() { return count; }
AbstractStringBuilder提供的諸如 charAt、replace、substring 等String類中同樣有的方法越界問題也是使用的count限制而不是value的實際長度;同時它提供了String類沒有的方法如:append、insert、delete以及reverse 等函式。
append 方法返回的是自身AbstractStringBuilder物件的引用,以 append(String str) 為例
public AbstractStringBuilder append(String str) { if (str == null) // 為空時新增"null" return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); // 確保value容量 當前已佔用 + str.length() str.getChars(0, len, value, count); count += len; return this; } private AbstractStringBuilder appendNull() { int c = count; ensureCapacityInternal(c + 4); // 確保value容量 當前已佔用 + "null".length() final char[] value = this.value; value[c++] = 'n'; value[c++] = 'u'; value[c++] = 'l'; value[c++] = 'l'; count = c; //已佔用數量增加 4 return this; } private void ensureCapacityInternal(int minimumCapacity) { // 確認容量是否需要擴容 // overflow-conscious code if (minimumCapacity - value.length > 0) { // 如果當前容量不足,通過需要容量計算出容量,複製資料至新陣列 value = Arrays.copyOf(value, newCapacity(minimumCapacity)); // 注意!!這裡的value被重新賦值了 } } private int newCapacity(int minCapacity) { // overflow-conscious code int newCapacity = (value.length << 1) + 2; // 擴容為 (原來的容量+1)* 2 if (newCapacity - minCapacity < 0) { // 擴容2倍仍不滿足,直接去需要的容量 newCapacity = minCapacity; } return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; }
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
AbstractStringBuilder 內部只有一個 abstract 函式,就是 toString() 函式,這個函式交給它的子類重寫。
@Override public abstract String toString();
AbstractStringBuilder 的子類使用了設計模式 建造者模式(將一個複雜物件的構建和它的表示相分離)。
三、StringBuilder 解析
StringBuilder 提供了4個建構函式,構造方法中的第一步是 呼叫初始化字元陣列父類建構函式初始化字元陣列的容量,預設是 16,當賦值String 或 CharSequence時,初始化為為它的長度 + 16,然後呼叫append方法,它的 append 方法直接使用的是 AbstractStringBuilder 的實現方式。
/** * Constructs a string builder with no characters in it and an * initial capacity of 16 characters. */ public StringBuilder() { super(16); } /** * Constructs a string builder with no characters in it and an * initial capacity specified by the {@code capacity} argument. * * @param capacity the initial capacity. * @throws NegativeArraySizeException if the {@code capacity} * argument is less than {@code 0}. */ public StringBuilder(int capacity) { super(capacity); } /** * Constructs a string builder initialized to the contents of the * specified string. The initial capacity of the string builder is * {@code 16} plus the length of the string argument. * * @param str the initial contents of the buffer. */ public StringBuilder(String str) { super(str.length() + 16); append(str); } /** * Constructs a string builder that contains the same characters * as the specified {@code CharSequence}. The initial capacity of * the string builder is {@code 16} plus the length of the * {@code CharSequence} argument. * * @param seq the sequence to copy. */ public StringBuilder(CharSequence seq) { this(seq.length() + 16); append(seq); }
StringBuilder 的字串操作方法 append、delete、insert、replace、indexOf、reverse 等方法都在 AbstractStringBuilder 中已實現,直接使用 super 呼叫,不同的是返回型別是StringBuilder型別的物件引用,不是 AbstractStringBuilder 型別的了。
前面講到,AbstractStringBuilder 中的 toString() 函式定義為抽象函式,沒有了方法體,需要由子類實現,StringBuilder 的 toString() 實現如下:
@Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
即新生成了一個String物件。
StringBuilder有兩個序列化與反序列化的方法 writeObject 和 readObject:
/** * Save the state of the {@code StringBuilder} instance to a stream * (that is, serialize it). * * @serialData the number of characters currently stored in the string * builder ({@code int}), followed by the characters in the * string builder ({@code char[]}). The length of the * {@code char} array may be greater than the number of * characters currently stored in the string builder, in which * case extra characters are ignored. */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { s.defaultWriteObject(); s.writeInt(count); // 儲存的value陣列實際使用的數量 s.writeObject(value); // 字元陣列 } /** * readObject is called to restore the state of the StringBuffer from * a stream. */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); count = s.readInt(); value = (char[]) s.readObject(); }
四、StringBuffer 解析
StringBuffer 出現的實際上比 StringBuilder 早,StringBuffer 在 JDK1.0 就已經推出,StringBuilder 到了JDK1.5 才推出。然而 StringBuffer 在JDK1.5時程式碼經過了完善,可以多執行緒使用,在單執行緒的情況想,StringBuilder更有優勢。
StringBuffer 在 AbstractStringBuffer 原有成員變數 value(char[]) 和 count (int) 增加了成員變數toStringCache(char[])
/** * A cache of the last value returned by toString. Cleared * whenever the StringBuffer is modified. */ private transient char[] toStringCache;
toStringCache 用於呼叫 toString()方法後字元數字中已使用長度(及count以內)的字元陣列的賦值,下次呼叫時不需要重新獲取。該變數使用 transient 修飾,表示該變數在序列化的時候不需要序列化該變數。在 StringBuffer 中的字串變更操作函式如 append、insert 、reverse 中,第一步就是 “toStringCache = null”。
toString() 函式實現如下:
@Override public synchronized String toString() { if (toStringCache == null) { toStringCache = Arrays.copyOfRange(value, 0, count); } return new String(toStringCache, true); // 返回的是通過容量count的得到的 toStringCache 生成的新字串 }
StringBuffer 中的構造方法與 StringBuilder 類似,同樣是4個建構函式:
public StringBuffer() { super(16); } public StringBuffer(int capacity) { super(capacity); } public StringBuffer(String str) { super(str.length() + 16); append(str); } public StringBuffer(CharSequence seq) { this(seq.length() + 16); append(seq); }
在其他方法中,除了 readObject 函式,所有的函式都直接或者間接的通過 synchronized 對函式進行了修飾,從而實現了執行緒同步,函式在實現內容上跟 StringBuilder 上沒有什麼差別,都是使用的 AbstractStringBuffer 已實現好的內容。
StringBuffer的序列化與反序列化相關程式碼實現如下:
private static final java.io.ObjectStreamField[] serialPersistentFields = { // 通過基本型別獲取相應包裝類的Class // public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int") // public static final Class<Boolean> TYPE = (Class<Boolean>) Class.getPrimitiveClass("boolean"); new java.io.ObjectStreamField("value", char[].class), new java.io.ObjectStreamField("count", Integer.TYPE), new java.io.ObjectStreamField("shared", Boolean.TYPE), }; // 方法通過 synchronized 修飾實現執行緒同步 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); s.writeFields(); } // 沒有通過 synchronized 修飾 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); }
toStringCache