天天看點

String的解析

String作為Java中最常用的引用類型,相對來說基本上都比較熟悉,無論在平時的編碼過程中還是在筆試面試中,String都很受到青睐,然而,在使用String過程中,又有較多需要注意的細節之處。

1.String是不可變類。

這句話其實大家都很熟悉了,那麼具體什麼是不可變類呢?一般認為:當對象一旦建立完成後,在正常情況下,對象的狀态不會因外界的改變而改變(對象的狀态是指對象的屬性,包括屬性的類型及屬性值)。

首先看一個基本的例子:

此時,初看上去,輸出的結果變了,發現s的值發生了變化,那麼這與上面的說法——String類是不可變類是否沖突呢?答案是否定的,因為s隻是指向堆記憶體中的引用,存儲的是對象在堆中的位址,而非對象本身,s本身存儲在棧記憶體中。

實際上,此時堆記憶體中依然存在着"abc"和"def"對象。對于"abc"對象本身而言,對象的狀态是沒有發生任何變化的。

那麼為什麼String類具有不可變性呢,顯然,既然不可變說明String類中肯定沒有提供對外可setters方法。接下來來具體看一下String類的定義。

下面是String類中主要屬性的定義(Java 1.7源碼):

String的解析
String的解析

與之前版本的Java String源碼相比,String類減少了int offset 和 int count的定義。這樣變化的結果主要展現在:

1.避免之前版本的String對象subString時可能引起的記憶體洩露問題;

2.新版本的subString時間複雜度将有O(1)變為O(n);

具體分析可見文章:

<a href="http://www.importnew.com/7656.html" target="_blank">http://www.importnew.com/7656.html</a>

<a href="http://www.importnew.com/14105.html" target="_blank">http://www.importnew.com/14105.html</a>

通過上面String類的定義,類名前面用了final class修飾,是以,String類不能被繼承。對于其屬性定義,可以看出,屬性value[]和hash都是被定義成private類型,且由于沒有 提供對外的public setters方法,String類屬性不可被改變。

其中,需要重點關注屬性value[],其被final char修飾,是以字元型數組value隻會被指派一次就不可修改。其存儲内容正好是String中的單個字元内容。

2.String相關的 +

String中的 + 常用于字元串的連接配接。此處為了說明清楚這個問題,首先可以安裝Eclipse 檢視位元組碼插件ByteCode Outline。

線上安裝網址: http://zipeditor.sourceforge.net/update/ Disabled。Help &gt;&gt; install new software &gt;&gt; 輸入網址 &gt;&gt; 選擇 bytecode outline &gt;&gt; ... ... 安裝成功。

Window &gt;&gt; show view &gt;&gt; other &gt;&gt; java &gt;&gt; ByteCode即可在Eclipse下方面闆欄中檢視。

看下面一個簡單的例子:

String的解析
String的解析

編譯運作後,點選ByteCode檢視,主要位元組碼部分如下:

String的解析
String的解析

顯然,通過位元組碼我們可以得出如下幾點結論:

1.String中使用 + 字元串連接配接符進行字元串連接配接時,連接配接操作最開始時如果都是字元串常量,編譯後将盡可能多的直接将字元串常量連接配接起來,形成新的字元串常量參與後續連接配接(通過反編譯工具jd-gui也可以友善的直接看出);

2.接下來的字元串連接配接是從左向右依次進行,對于不同的字元串,首先以最左邊的字 符串為參數建立StringBuilder對象,然後依次對右邊進行append操作,最後将StringBuilder對象通過toString()方 法轉換成String對象(注意:中間的多個字元串常量不會自動拼接)。

也就是說

String c = "xx" + "yy " + a + "zz" + "mm" + b; 實質上的實作過程是: String c = new StringBuilder("xxyy").append(a).append("zz").append("mm").append(b).toString();

由于得出結論:當使用+進行多個字元串連接配接時,實際上是産生了一個StringBuilder對象和一個String對象。

 3.String中的常用方法

1).與String類中value[]數組存儲直接相關的有:

int length(); // 傳回String長度,亦即value[]數組長度;

char charAt(int index); // 傳回指定位置字元;

int indexOf(int ch, int fromIndex); //從fromIndex位置開始,查找ch字元在字元串中首次出現的位置。fromIndex預設為0,ch直接傳入字元即可。如'C',區分大小寫,未查找到傳回-1;

char[] toCharArray() ;   // 将字元串轉換成一個新的字元數組

2).與其他字元串即字串相關的方法有:

int indexOf(String str, int fromIndex) ;

與indexOf含義相反有lastIndexOf(..),反向索引。

boolean contains(String str); //實際上 contains内部實作也是調用的indexOf,然後将其結果與-1相比較。

boolean startsWith(String str); // 判斷字元串是否以str開頭

boolean endsWith(String str); //.....是否以str結尾

String replace(CharSequence target, CharSequence replacement) ;  // 替換

String substring(int beginIndex,  int endIndex);  //字元串截取,不傳第二個參數則表示直接截取到字元串末尾

String[] split(String regex);  // 字元串分割

4.String中的equals()與hashCode()

5.String字元串常量池

JVM為了提高性能和減少記憶體開銷,内部維護了一個字元串常量池,每當建立字元串常量時,JVM首先檢查字元串常量池,如果常量池中已經存在,則傳回池中的字元串對象引用,否則建立該字元串對象并放入池中。

是以下述結果傳回true。

但與建立字元串常量方式不同的是,當使用new String(String str)方式等建立字元串對象時,不管字元串常量池中是否有與此相同内容的字元串,都會在堆記憶體中建立新的字元串對象。

是以,下面代碼片段有如下結果。

即使字元串内容相同,字元串常量池中的字元串與通過new String(..)等方式建立的字元串對象之間沒有直接的關系,但是,可以通過字元串的intern()方法找到此種關聯。intern()方法傳回字元串對象在字元串常量池中的對象引用,若字元串常量池中尚未有此字元串,則建立一新的字元串常量放置于池中。

于是,很據如上了解,很自然的,可以得到如下結果。

String的解析
String的解析

6.String/StringBuilder/StringBuffer差別

String是不可變字元串對象,StringBuilder和StringBuffer是可變字元串對象(其内部的字元數組長度可變),StringBuffer線程安全,StringBuilder非線程安全。

7.既然String是不可變字元串對象,如何才能改變讓其可變?

既然String對象中沒有對外提供可用的public setters等方法,是以隻能通過Java中的反射機制實作。是以,前文中說到的String是不可變字元串對象隻是針對“正常情況下”。而非必然。

String的解析
String的解析

由此可見Java中反射的強大之處。

繼續閱讀