作者:SmlAnt出處:http://www.cnblogs.com/smlAnt一、概述String是我們平常用得最多的基元類型之一,雖然我們經常使用而且感到非常熟悉;但很多朋友只知道一個字元串的定義、使用或知道如何使用StringBuilder來達到高效構建字元串,但是有多少朋友有興趣去瞭解背後...
作者:SmlAnt
出處:http://www.cnblogs.com/smlAnt
一、概述
String是我們平常用得最多的基元類型之一,雖然我們經常使用而且感到非常熟悉;但很多朋友只知道一個字元串的定義、使用或知道如何使用StringBuilder來達到高效構建字元串,但是有多少朋友有興趣去瞭解背後的一些“不為我們知道的秘密”?
二、為什麼把String加入到基元類型中
在以前的面向過程語言中,並沒有String這個類型,定義一個字元串的方式則採用一個Char[],雖然提供了對字元串的操作、比較等函數,但是還是不夠方便也不太符合面向對象做法,所以在面向對象語言中,string也加入了基元類型的隊列中;
三、String核心特征immutable(不可變)
代表一個不可變的順序字元集,也就是說一經創建,字元串編不能以任何方式進行修改;具有以下3個優點:
1. 允許在一個字元串上執行各種操作,而不實際地更改字元串;
2. 在操作或訪問一個字元串的時候不會發生線程同步的問題;
3. 基於性能的考慮,String類型與CLR緊密集成,CLR知道String類型中定義的欄位如何佈局,而且CLR會直接訪問,所以開發的時只好將String定義為密封類(Sealed);
四、被重寫的兩個方法GetHashCode與Equals
1. GetHashCode方法進行了重寫,目的是為了滿足兩個字元串的判斷;
2. Equals方法進行重寫,其中Equals方法最終還是調用了GetHashCode方法來進行判斷;
五、“==”、Equals、Compare,字元串判斷到底用哪個好點?
1. String對“==”操作符進行重載,內部實現進行了空值判斷後,再調用Equals進行判斷(所以採用"=="操作符,不會拋出空指針異常)
2. String的Equals方法,因為調用Equals方法的時候,直接通過返回HashCode進行比較,效率最高,有可能對象為空,有可能會拋空指針異常,所以用的時候需要留意;
3. String的Compare方法:該方法是一個靜態方法,內部實現是首先判斷字元串的長度是否相等,如果長度不相等,直接返回結果,如果長度相等,則會採用逐個字元進行判斷,如果方法中的CultureInfo不為空,則判斷的過程中會逐個字元進行展開(這裡涉及到語言的問題,如果採用德語會把"β"展開為"ss",所以”strasse”跟”staβe”的判斷結果是相同的);
註:如果一般情況下,建議採用Equals進行判斷,效率最高,但如果無法確保方法Equals的調用者是否不為null,建議還是採用==或者 "XXXX".Equals(obj),如果需要用到多語言(國際化)判斷的時候,可以考慮用Compare;
六、拘留池(Interning)
字元串操作(比如Compare)的做法是很多程式常見的操作,這樣的操作可能造成記憶體中複製同一個字元串的多個實例(演算法內部操作導致),為了達到節省記憶體的效果,CLR採用了一種叫“字元串留用的技術”(String Interning),開闢了一塊名為“拘留池”的空間專門用於存放字元串,而拘留池在程式初始化的時候,會把元數據預設載入到“拘留池”中,而且不會受到垃圾回收器的影響,只有在程式被關閉的時候才會釋放“拘留池”中的資源;
七、存儲方式,大致分為以下3種:
1. 以常量的方式來定義很保存字元串,比如:var value = "abc";
2. 以對象的方式來保存到堆中:比如 var value = new string('a');
3. 以對象的方式構造然後存放到拘留池(其實常量的方式定義後,預設也會把字元串加入到駐留池中);
可以參考以下代碼和記憶體分配圖進行理解:
//驗證字元串常量預設載入到元數據,預設會把元數據中的字元串載入到拘留池var data = "abc"; //此聲明方式,會把該變數定義為字元串常量,然後存入元數據中
var a = "a"; //同上
var b = "b"; //同上
var c = "c"; //同上
var ab = a + b; //根據線程棧上的a、b地址獲取到堆上的a、b實例,然後把兩個實例的結果進行運算後產生一個新對象,最後新對象地址賦給ab變數
var abc = a + b + c; //同上
var abResult = string.IsInterned(ab); //返回結果為null,也就是說沒有把字元串"ab"存入拘留池中;
var abcResult = string.IsInterned(abc); //返回結果為"abc",也就是說已經把字元串(這裡是常量)"abc"存放到拘留池中;
var empty = string.Empty; //初始化的時候,從String類型對象中獲取靜態屬性Empty的數據;
var strEmpty = ""; //產生一個值為空的String對象; var a1 = "abc";
var a2 = string.IsInterned(a1);
var result = object.ReferenceEquals(a1, a2);
註:預設是不從拘留池中載入,而是直接採用ldstr的特殊指令獲得字元串”abc”,但可以確認的是”元數據”跟“拘留池”中的字元串是用個對象;
八、案例分析
1. 以下代碼的HashCode是否相同,它們是否是同個對象; var A = "ab" + "c";
var B = "abc";
2. 以下代碼的HashCode是否相同,他們是否是同個對象:
var A = Console.ReadLine(); //輸入"abc"
var B = Console.ReadLine(); //輸入"abc"
3. 以下代碼的HashCode是否相同,他們是否是同個對象:
var A = Console.ReadLine(); //輸入"abc"
var B = Console.ReadLine(); //輸入"abc"
var A = string.Intern(B);