跟我一起讀源碼(java)—String、StringBuffer、StringBuilder

寫在前面:之所以寫這個系列,是因爲實習期間相對還算不太忙,想看看源碼,學習學習,僅此而已,寫的不對的地方,還請大家多多指正(博主用的版本是1.8.0)。。。

我不知道如何學習源碼,網上源碼談的比較多的都是一些比較牛逼的人,而我,僅僅一個小白,太複雜的註定看不懂,所以只能找自己的方法去學習,想用自己的路線慢慢去消化、瞭解,我的辦法很直白,就從我們最初學習java開始一步步深入去了解我們一路上所接觸到的源碼,好了,毋庸置疑,我們大部分人接觸到的第一個java小例子估計就是下面這段小程序了吧:

public class Main {
public static void main(String[] args) {
System.out.println("Hello,word!");
}
}

這段代碼大家都很熟悉,也沒有多少說的,那麼我想大家也肯定知道我要先從誰說起了,對,沒錯,就是String類!

String類:
首先看看String類的定義:

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {...}

這一段很簡潔的給我們交待了String類幾個比較重要的特性:
1)String類是final的,也就是說String類不允許被繼承、不能被修改(在Java中,被 final類型修飾的類不允許被其他類繼承,被 final修飾的變量不允許被修改)
2)String類實現了三個接口:Serializable Comparable CharSequence,展開來說,
a、實現了IO流的Serializable接口,表明String類的對象可被序列化和反序列化。Java的序列化機制是通過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體(類)的serialVersionUID進行比較,如果相同就認爲是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常(InvalidCastException),所以在開發中,我們一定記得顯式的聲明UID。。。。
b、實現Comparable接口,表明String類的對象進行整體排序,可以進行自定義的字符串比較,定義的泛型爲String類,說明給Comparable的數據類型只能是String類
c、實現CharSequence接口,用於表明char值得一個可讀序列,此接口對許多不同種類的 char 序列提供統一的只讀訪問。

往下看是String類的一些屬性:

/** The value is used for character storage. */
private final char value[]; // char[] 用來存放String類型的字符數組,注意的是這裏value是final的,所以賦值後不能被修改,這是字符串不能被修改的原因!

/** Cache the hash code for the string */
private int hash; // Default to 0 表示String字符的hash值 默認爲0

/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L; // 序列化serialVersionUID

緊接着,我們可以看到String類的構造方法:

//默認無參構造方法,調用這個構造方法可以構造一個空的字符串對象 沒有多大意義(因爲字符串值是final的,不可變)
public String() {
this.value = "".value;
}
//有參構造方法,接收一個字符串對象orginal作爲參數,並用它初始化新創建的字符串對象,使新創建的字符串是該參數字符串的副本,除非需要original的顯式副本,否則沒必要使用此構造函數。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
//使用一個字符數組創建一個新的字符串以表示字符數組參數中當前包含的字符序列,字符數組value的內容已被複制到字符串對象中,所以後續對字符數組的修改不會影響到已經創建好的字符串。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
//該構造方法會分配一個新字符串,初始值取自字符數組value,offset參數是子數組第一個字符的索引,count參數指定子數組的長度。當count=0且offset<=value.length時,會返回一個空的字符串。
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
//這個方法只是單純的進行邊界檢查,length、offset不能小於零,而且offset+lenght不能超出字節數組的長度。
/* Common private utility method used to bounds check the byte array
* and requested offset & length values used by the String(byte[],..)
* constructors.
*/
private static void checkBounds(byte[] bytes, int offset, int length) {
if (length < 0)
throw new StringIndexOutOfBoundsException(length);
if (offset < 0)
throw new StringIndexOutOfBoundsException(offset);
if (offset > bytes.length - length)
throw new StringIndexOutOfBoundsException(offset + length);
}
//這兩個構造函數使用指定的字符集解碼字節數組,構造一個新的字符串。解碼的字符集可以使用字符集名指定或者直接將字符集傳入。注意,如果給定的字符集無效,構造函數的行爲沒有指定。 
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}

public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charset, bytes, offset, length);
}
//這個decode方法屬於Stringcoding類 使用的解碼的字符集就是我們指定的charsetName或者charset。 我們在使用byte[]構造String的時候,如果沒有指明解碼使用的字符集的話,那麼StringCoding的decode方法首先調用系統的默認編碼格式,如果沒有指定編碼格式則默認使用ISO-8859-1編碼格式進行編碼操作。
static char[] decode(String charsetName, byte[] ba, int off, int len)
throws UnsupportedEncodingException
{
StringDecoder sd = deref(decoder);
String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
if ((sd == null) || !(csn.equals(sd.requestedCharsetName())
|| csn.equals(sd.charsetName()))) {
sd = null;
try {
Charset cs = lookupCharset(csn);
if (cs != null)
sd = new StringDecoder(cs, csn);
} catch (IllegalCharsetNameException x) {}
if (sd == null)
throw new UnsupportedEncodingException(csn);
set(decoder, sd);
}
return sd.decode(ba, off, len);
}
//除了前面所示的,可以從字符串、字符數組、代碼點數組、字節數組構造字符串外,也可以使用StringBuffer和StringBuilder構造字符串。
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}

public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

​這裏提到StringBuffer,我們進入這個類可以看到如下主要代碼,具體不多說,值得一提的是StringBuffer給每個方法上都利用Synchronized關鍵字進行了同步操作,其中StringBuilder類和StringBuffer大致一樣,只是沒有加Synchronized關鍵字,所以沒有實現同步,在單線程時效率比較高所以這裏不重複書寫代碼了!

public final class StringBuffer extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{...}
//默認爲16個字符
public StringBuffer() {
super(16);
}
//以指定capacity大小初始化
public StringBuffer(int capacity) {
super(capacity);
}
【 這裏super 調用父類進行初始化 這裏是按照指定容量創建字符數組  其他的不多說 參照這個即可。
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
】
//初始化爲內容爲指定str,初始化容量爲str的大小+16
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
//以下操作方法都利用了Synchronized關鍵字
@Override
public synchronized int length() {
return count;
}
@Override
public synchronized int capacity() {
return value.length;
}
@Override
public synchronized void ensureCapacity(int minimumCapacity) {
super.ensureCapacity(minimumCapacity);
}
@Override
public synchronized void trimToSize() {
super.trimToSize();
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @see #length()
*/
@Override
public synchronized void setLength(int newLength) {
toStringCache = null;
super.setLength(newLength);
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @see #length()
*/
@Override
public synchronized char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}

接下來還需要看下StringBuffer中的append方法,這裏就舉其中一個方法爲例,其他重載方法還有Object、String、AbstractStringBuilder等類型的,具體的話,大家自行查看,代碼比較簡單 就不一一解釋了 大家知道怎麼添加的就好,下面示例代碼是按照在類中調用順序依次查看的。。。

public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
//StringBuffer繼承自AbstractStringBuilder類
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;//這裏我們看到這個字符數組value沒有背final修飾 與String類中不同 所以可以修改他們的引用變量的值 即可以引用到新的數組對象 所以其對象是可變的 就是從這裏得出的
int count;//這個count用來記錄字符個數
.... }
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
//這裏如果count+len>len 則進行擴容,所以指定合適大小很重要,因爲能減少擴容次數(要知道創建字符數組對象和複製都是要開銷的!)
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
//Arrays.copyOf(value,newCapacity)複製指定數組,截取或用null字符填充以使副本具有指定長度。
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}

public synchronized void getChars(int srcBegin, int srcEnd, char[] dst,
int dstBegin)
{
super.getChars(srcBegin, srcEnd, dst, dstBegin);
}
//將字符從此字符串複製到目標字符數組dst中,第一二個參數截取要添加字符串的長度,第三個爲目標字符數組,第四個爲字符串要添加到數組的開始位置
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");
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

PS:這裏順帶總結下String、StringBuffer、StringBuilder:
1)可變與不可變
  String類中使用字符數組private final char value[]保存字符串,因爲有“final”修飾符,所以可以知道string對象是不可變的。
  StringBuilder與StringBuffer都繼承自AbstractStringBuilder類,在AbstractStringBuilder中也是使用字符數組保存字符串,char[] value;可知這兩種對象都是可變的。
2)是否多線程安全
  String中的對象是不可變的,也就可以理解爲常量,顯然線程安全。
  AbstractStringBuilder是StringBuilder與StringBuffer的公共父類,定義了一些字符串基本操作,如append、insert、indexOf等公共方法。
  StringBuffer對方法加了同步鎖或者對調用的方法加了同步鎖,所以是線程安全的。
  StringBuilder並沒有對方法進行加同步鎖,所以是非線程安全的。
3)StringBuilder與StringBuffer共同點
  StringBuilder與StringBuffer有公共父類AbstractStringBuilder(抽象類)。
  StringBuilder、StringBuffer的方法都會調用AbstractStringBuilder中的公共方法,如super.append(…)。只是StringBuffer會在方法上加synchronized關鍵字,進行同步。
  PS:如果程序非多線程,那麼StringBuilder效率高於StringBuffer,三者在執行速度方面的比較:StringBuilder > StringBuffer > String
具體總結可以參考:點這裏查看詳細總結

接下來繼續回到String類,讀到length()方法:

public int length() { //length()方法返回字符串的長度,即字符串中Unicode代碼單元的數量。
return value.length;
}
public boolean isEmpty() {  // 判斷字符串是否爲空。
return value.length == 0;
}

//返回指定索引處的字符,索引範圍爲從0到lenght()-1。如果索引指定的char值是代理項,則返回代理項值。
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}

public int codePointAt(int index) {  // 返回索引index處的代碼點
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}

intern 方法,
public native String intern();
在intern方法中看到,這個方法是一個 native 的方法,但註釋寫的非常明瞭。“如果常量池中存在當前字符串, 就會直接返回當前字符串. 如果常量池中沒有此字符串, 會將此字符串放入常量池中後, 再返回”。

還有例如getChar()方法之前在SringBuffer中有提及,就不多加解釋了。。。
另外還有幾個getBytes()方,平時用的不多也不貼出來了,這三個方法都是將String編碼爲byte序列存儲到byte數組後返回,都是調用StringCode.encode方法實現。方法的不同之處是字符集指定方式,分別是字符集名字、字符集和平臺默認字符集,大家自行查看源碼即可。。。
​最後我們着重來看幾個常用的方法:

equals()方法:

public boolean equals(Object anObject) {
if (this == anObject) { //如果是同一個對象,直接返回true
return true;
}
if (anObject instanceof String) { //如果是一個String類的實例 則繼續向下比較
String anotherString = (String)anObject; // 轉換爲String類型
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;//只有類型 長度 元素值都相等的情況才返回true
}
}
return false;
}
另外有一個不管大小寫的比較:
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}

最後再讀一個compareTo()方法,String實現了Comparable接口:

public int compareTo(String anotherString) {
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;//返回Unicode值的差
}
k++;
}
return len1 - len2;//當前n個字符都相同時,返回兩個字符串的長度的差  
}

Hashcode()方法

public int hashCode() { //採用乘法hash算法,字符串相同hash值一定相同,hash值相同,不一定字符串相同
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;

for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i]; // 每一次hash值*31+該字符
}
hash = h;
}
return h;
}

這裏順道總結下equals和hashcode:
Java中equals()和hashCode()有一個契約:
1. 如果兩個對象相等的話,它們的hashcode必須相等;
2. 但如果兩個對象的hashcode相等的話,這兩個對象不一定相等。
3.
講到這,突然想起另外幾個方法:replace和substring ,另外還有幾個簡單的不列出源代碼了:split、toString、format、indexof之類的
replace:舊值替換成新值。。。

public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */

while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}
// 這個是支持正則表達式替換的
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

str=str.substring(int beginIndex);截取掉str從首字母起長度爲beginIndex的字符串,將剩餘字符串賦值給str;
str=str.substring(int beginIndex,int endIndex);截取str中從beginIndex開始至endIndex結束時的字符串,並將其賦值給str,這裏尤其要記住含前不含後;

public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
} 

本來通過這個例子是還要把System類和println方法也拿出來講講的,由於事情緣故,就先寫這麼些,回頭拿這個例子接着讀。。。。。
代碼格式有點蹩腳 不處理了。。。。
——by白附子(小仇哥)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章