更新時(shí)間:2020-10-30 17:40:02 來源:動(dòng)力節(jié)點(diǎn) 瀏覽1430次
字符串駐留是一種僅保存一份相同且不可變字符串的方法。字符串駐留機(jī)制,其實(shí)就是系統(tǒng)維護(hù)interned字典,記錄已被駐留的字符串對象。
下面我們通過實(shí)際例子探討字符串駐留機(jī)制:
Code Snip:
static void Main(string[] args)
{
string str1 = "ABCD1234";
string str2 = "ABCD1234";
string str3 = "ABCD";
string str4 = "1234";
string str5 = "ABCD" + "1234";
string str6 = "ABCD" + str4;
string str7 = str3 + str4;
Console.WriteLine("string str1 = \"ABCD1234\";");
Console.WriteLine("string str2 = \"ABCD1234\";");
Console.WriteLine("string str3 = \"ABCD\";");
Console.WriteLine("string str4 = \"1234\";");
Console.WriteLine("string str5 = \"ABCD\" + \"1234\";");
Console.WriteLine("string str6 = \"ABCD\" + str4;");
Console.WriteLine("string str7 = str3 + str4;");
Console.WriteLine("\nobject.ReferenceEquals(str1, str2) = {0}", object.ReferenceEquals(str1, str2));
Console.WriteLine("object.ReferenceEquals(str1, \"ABCD1234\") = {0}", object.ReferenceEquals(str1, "ABCD1234"));
Console.WriteLine("\nobject.ReferenceEquals(str1, str5) = {0}", object.ReferenceEquals(str1, str5));
Console.WriteLine("object.ReferenceEquals(str1, str6) = {0}", object.ReferenceEquals(str1, str6));
Console.WriteLine("object.ReferenceEquals(str1, str7) = {0}", object.ReferenceEquals(str1, str7));
Console.WriteLine("\nobject.ReferenceEquals(str1, string.Intern(str6)) = {0}", object.ReferenceEquals(str1, string.Intern(str6)));
Console.WriteLine("object.ReferenceEquals(str1, string.Intern(str7)) = {0}", object.ReferenceEquals(str1, string.Intern(str7)));
}
下邊是輸出的結(jié)果:

接下來我們來逐句地分析這段代碼:
首先我們創(chuàng)建了兩個(gè)完全相同的字符串(ABCD1234),并將他們分別賦予了兩個(gè)字符創(chuàng)變量——str1和str2。然后把它們傳給了object.ReferenceEquals。我們知道object.ReferenceEquals是用于確定兩個(gè)變量是否具有相同的引用——換句話說,當(dāng)兩個(gè)變量引用的是同一塊托管推的內(nèi)存快的時(shí)候,返回True,否則返回False。
string str1 = "ABCD1234";
string str2 = "ABCD1234";
object.ReferenceEquals(str1, str2)= True;
object.ReferenceEquals(str1, "ABCD1234")) = True;
令我們感到奇怪的是,當(dāng)我們分別創(chuàng)建的引用類型兩個(gè)變量——string是引用類型。照理說CLR會(huì)在托管堆(Managed Heap)中為它們分配兩段內(nèi)存快,他們不可能具有相同的引用才對,但是為什么object.ReferenceEquals 方法會(huì)返回True呢。而對于第二個(gè)比較——一個(gè)字符串變量和一個(gè)和他具有相同內(nèi)容的字符串("ABCD1234";)直接進(jìn)行比較,按照我們對CLR內(nèi)存的分配的一般理解,應(yīng)該是CLR首先會(huì)在托管堆中為這段字符串("ABCD1234")分配內(nèi)存快,然后把相對應(yīng)的引用傳遞給object.ReferenceEquals方法(由于分配在托管堆的這段字符串并沒有被任何變量引用,所以當(dāng)垃圾回收的時(shí)候會(huì)被回收掉),所以無論如何也不應(yīng)該返回True。
我們先把問題留到最后,接著分析我們的Sample。上面?zhèn)儗ψ址兞恐g以及變量與字符串之間進(jìn)行了比較,如果我們對一個(gè)字符串變量和一個(gè)動(dòng)態(tài)創(chuàng)建的字符串(通過+Operator把兩個(gè)字符串連接起來)進(jìn)行比較,結(jié)果又會(huì)如何呢?我們來看看下面的偽代碼演示:
string str3 = "ABCD";
string str4 = "1234";
string str5 = "ABCD" + "1234";
string str6 = "ABCD" + str4;
string str7 = str3 + str4;
object.ReferenceEquals(str1, str5) = True
object.ReferenceEquals(str1, str6) = False
object.ReferenceEquals(str1, str7)) = False
在上面的例子中,我們用三種不同的方式創(chuàng)建了3個(gè)字符串變量(str5,str6,str7)——string+string;string+variable;variable+variable。然后分別和我們已經(jīng)創(chuàng)建的、和它們具有相同字符串“值”的變量(str1)作比較。同樣令我們感到奇怪的是第一個(gè)返回True,而后兩個(gè)則為False。帶著這些疑惑我們來看看對于string這一特殊的類型說采用的特殊的使用機(jī)制。
1.System.String雖然是一個(gè)引用類型,但是它具有其自身的特殊性。我們最容易想到的是它創(chuàng)建的特殊性——一般的對象在創(chuàng)建的時(shí)候需要通過new關(guān)鍵字調(diào)用對應(yīng)的構(gòu)造函數(shù)來實(shí)現(xiàn);而創(chuàng)建一段string不需要這么做——我們只需要把對應(yīng)的字符換賦給給對應(yīng)的字符串變量就可以了。之所以存在著這種差異,是因?yàn)樗麄冊趧?chuàng)建過程中使用的IL指令時(shí)不同的——一般的引用對象的創(chuàng)建是通過newobj這樣一個(gè)IL指令來實(shí)現(xiàn)的,而創(chuàng)建一個(gè)字符串變量的IL指令則是ldstr (load string)。(象C#,VB.NET這樣的語言畢竟是高級語言,進(jìn)行了高度的抽象,站在這樣的角度分析問題往往不能夠看到其實(shí)質(zhì),所以有時(shí)候我們把應(yīng)該從交底層上面找突破口——比如分析IL,Metadata…);
2. 由于String是我們做到頻率最高的一種類型,CLR考慮性能的提升和內(nèi)存節(jié)約上,對于相同的字符串,一般不會(huì)為他們分別分配內(nèi)存塊,相反地,他們會(huì)共享一塊內(nèi)存。CLR實(shí)際上采用這個(gè)的機(jī)制來實(shí)現(xiàn)的:CLR內(nèi)部維護(hù)著一塊特殊的數(shù)據(jù)結(jié)構(gòu)——我們可以把它看成是一個(gè)Hash table,這個(gè)Hash table維護(hù)者大部分創(chuàng)建的string(我這里沒有說全部,因?yàn)橛刑乩?。這個(gè)Hash table的Key對應(yīng)的相應(yīng)的string本身,而Value則是分配給這個(gè)string的內(nèi)存塊的引用。當(dāng)CLR初始化的時(shí)候創(chuàng)建這個(gè)Hash table。一般地,在程序運(yùn)行過程中,如果需要的創(chuàng)建一個(gè)string,CLR會(huì)根據(jù)這個(gè)string的Hash Code試著在Hash table中找這個(gè)相同的string,如果找到,則直接把找到的string的地址賦給相應(yīng)的變量,如果沒有則在托管堆中創(chuàng)建一個(gè)string,CLR會(huì)先在managed heap中創(chuàng)建該strng,并在Hash table中創(chuàng)建一個(gè)Key-Value Pair——Key為這個(gè)string本身,Value位這個(gè)新創(chuàng)建的string的內(nèi)存地址,這個(gè)地址最重被賦給響應(yīng)的變量。這樣我們就能解釋上面的疑問了。
string str1 = "ABCD1234";
string str2 = "ABCD1234";
object.ReferenceEquals(str1, str2)= True;
object.ReferenceEquals(str1, "ABCD1234")) = True;
當(dāng)創(chuàng)建str1的時(shí)候,CLR現(xiàn)在我們上面提到的Hash table中找“ABCD1234”這樣的一個(gè)string,沒有找到,則在托管堆中為這個(gè)string分配一塊內(nèi)存,然后在Hash table為該string添加一個(gè)Key-Value Pair。接著創(chuàng)建str2,CLR仍然會(huì)在Hash table找ABCD1234這樣的一個(gè)string,這回它會(huì)找到我們新創(chuàng)建的這個(gè)Entry,所以這個(gè)Key-Value Pair中Value(string的地址)會(huì)賦給str2。因?yàn)閟tr1和str2 具有相同的引用,所以調(diào)用object.ReferenceEquals返回True。同理當(dāng)我們對str1和"ABCD1234"進(jìn)行比較的時(shí)候,str1直接傳入該方法,放傳入"ABCD1234"這個(gè)字符串的時(shí)候,CLR同樣會(huì)在Hash table找ABCD1234這樣的一個(gè)string,相同的Entry被找到,這個(gè)Entry(Key-Value Pair)的Value(string的地址)被傳到object.ReferenceEquals,所以他們?nèi)匀幌嗤囊?,結(jié)果返回True。
3. 并非所有的情況下字符串的駐留都會(huì)起作用。對于對一個(gè)動(dòng)態(tài)創(chuàng)建的字符串(比如string+variable;variable+variable),這種駐留機(jī)制便不會(huì)起作用。因?yàn)閷τ谶@樣的字符串,是不會(huì)被添加到內(nèi)部的Hash table中的。但是對于string+string則不同,因?yàn)楫?dāng)這樣的語句被編譯成IL的時(shí)候,編譯器是先把結(jié)構(gòu)計(jì)算出來,然后再調(diào)用ldstr指令——而對于string+variable;variable+variable這種情況,所對應(yīng)的IL指令是Concat。所以對于string+string字符串的駐留仍然有效。
比如對于以下一段代碼:
static void Main(string[] args)
{
string str1 = "ABC";
string str2 = str1 + "123";
string str3 = "ABC" + "123";
}
對應(yīng)的IL Code是:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 26 (0x1a)
.maxstack 2
.locals init ([0] string str1,
[1] string str2,
[2] string str3)
IL_0000: nop
IL_0001: ldstr "ABC"
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldstr "123"
IL_000d: call string [mscorlib]System.String::Concat(string,
string)
IL_0012: stloc.1
IL_0013: ldstr "ABC123"
IL_0018: stloc.2
IL_0019: ret
} // end of method Program::Main
所以現(xiàn)在我們就可以解釋第二個(gè)疑問了。
雖然對于對一個(gè)動(dòng)態(tài)創(chuàng)建的字符串(比如string+variable;variable+variable),字符串駐留機(jī)制便不會(huì)起作用。但是我們可以手工的啟用駐留機(jī)制——那就是調(diào)用定義的System.String中的靜態(tài)方法Intern。這個(gè)方法接受一個(gè)字符串作為他的輸入?yún)?shù),返回的經(jīng)過駐留處理的string。他的實(shí)現(xiàn)機(jī)制是:如果能在內(nèi)部的Hash Table中找到傳入的string,則返回對應(yīng)的string引用,否則就在Hash Table添加該string對應(yīng)的Entry,并返回string的引用。
以上就是對字符串駐留機(jī)制的講解,通過一步一步的分析我們知道了字符串駐留機(jī)制的本質(zhì)。字符串駐留機(jī)制只是字符串里面很小的一個(gè)知識(shí)點(diǎn),在本站的Java基礎(chǔ)教程里面還有更多的字符串的相關(guān)知識(shí)等你來學(xué)!

初級 202925

初級 203221

初級 202629

初級 203743