您好,登錄后才能下訂單哦!
前言
string是一種很特殊的數據類型,它既是基元類型又是引用類型,在編譯以及運行時,.Net都對它做了一些優化工作,正式這些優化工作有時會迷惑編程人員,使string看起來難以琢磨。本文將給大家詳細介紹關于C#字符串優化String.Intern、IsInterned的相關內容,分享出來供大家參考學習,下面話不多說了,來一起看看詳細的介紹吧。
首先看一段程序:
using System; class Program { static void Main(string[] args) { string a = "hello world"; string b = a; a = "hello"; Console.WriteLine("{0}, {1}", a, b); Console.WriteLine(a == b); Console.WriteLine(object.ReferenceEquals(a, b)); } }
這個沒有什么特殊的地方,相信大家都知道運行結果:
hello, hello world False False
第二個WriteLine使用==比較兩個字符串,返回False是因為他們不一致。而最后一個WriteLine返回False,因為a、b的引用不一致。
接下來,我們在代碼的最后添加代碼:
Console.WriteLine((a + " world") == b); Console.WriteLine(object.ReferenceEquals((a + " world"), b));
這個的輸出,相信也不會出乎大家的意料。前者返回True,因為==兩邊的內容相等;后者為False,因為+運算符執行完畢后,會創建一個新的string實例,這個實例與b的引用不一致。
上面這些就是對象的通常工作方式,兩個獨立的對象可以擁有同樣的內容,但他們卻是不同的個體。
接下來,我們就來說一下string不尋常的地方
看一下下面這段代碼:
using System; class Program { static void Main(string[] args) { string hello = "hello"; string helloWorld = "hello world"; string helloWorld2 = hello + " world"; Console.WriteLine("{0}, {1}: {2}, {3}", helloWorld, helloWorld2, helloWorld == helloWorld2, object.ReferenceEquals(helloWorld, helloWorld2)); } }
運行一下,結果為:
hello world, hello world: True, False
再一次,沒什么意外,==返回true因為他們內容相同,ReferenceEquals返回False因為他們是不同的引用。
現在在后面添加這樣的代碼:
helloWorld2 = "hello world"; Console.WriteLine("{0}, {1}: {2}, {3}", helloWorld, helloWorld2, helloWorld == helloWorld2, object.ReferenceEquals(helloWorld, helloWorld2));
運行,結果為:
hello world, hello world: True, True
等一下,這里的hellowWorld與helloWorld2引用一致?這個結果,相信很多人都有些接受不了。這里的helloWorld2與上面的hello + " world"應該是一樣的,但為什么ReferenceEquals返回的是True?
String.Intern
有經驗的程序員們,應該知道,一個大型項目中,字符串的數量是巨大的。有些時候會出現幾百、幾千、甚至幾萬的重復字符串存在。這些字符串的內容相同,但卻會重復分配內存,占用巨額的存儲空間,這個肯定是要優化處理的。而C#在處理這個問題的時候,采用的就是普遍的做法,建立內部的池,池中每一個不同的字符串存在唯一一個個體在池中(這個方案在各種大型項目中都能見得到)。而C#畢竟是一種語言,而不是一個面向某個具體領域的技術,所以,它不能將這種內部的池技術,做成全部自動化的。因為我們不知道,將來C#會被使用到何種規模的項目中。如果完全自動化維護這個內部池,可能會在大型項目中,造成內存的巨大浪費,畢竟不是所有的字符串都有必要加到這個常駐的池中的。于是,C#提供了String.Intern和String.IsInterned接口,交給程序員自己維護內部的池。
String.Intern的工作方式很好理解,你將一個字符串作為參數使用這個接口,如果這個字符串已經存在池中,就返回這個存在的引用;如果不存在就將它加入到池中,并返回引用,例如:
Console.WriteLine(object.ReferenceEquals(String.Intern(helloWorld), String.Intern(helloWorld2)));
這段代碼將返回True,盡管helloWorld與helloWorld2的引用不同,但他們的內容相同。
這里我們花幾分鐘,測試一下String.Intern,因為在某些情況下,它產生的結果,有點違反直覺。這里是一個例子:
string a = new string(new char[] {'a', 'b', 'c'}); object o = String.Copy(a); Console.WriteLine(object.ReferenceEquals(o, a)); String.Intern(o.ToString()); Console.WriteLine(object.ReferenceEquals(o, String.Intern(a)));
第一個WriteLine返回False很好理解,因為String.Copy創建了一個a的新的實例,所以,o與a的引用不用。
但為什么第二個WriteLine返回的是True?思考一下吧,下面再看一個例子:
object o2 = String.Copy(a); String.Intern(o2.ToString()); Console.WriteLine(object.ReferenceEquals(o2, String.Intern(a)));
這個看起來,與上面的做了同樣的事,但為什么WriteLine返回的是False?
首先,需要說明一下ToString的工作方式,它總是返回它自身的引用。o是一個指向“abc”的變量,調用ToString返回的就是這個引用。所以,對于上面的內容,可以這樣解釋:
String.Intern(o.ToString())
將對象#2的引用添加到內部池中。String.Intern(a)
返回的是#2的引用。String.Intern(o2.ToString())
沒有向內部池中添加任何內容,因為“abc”已經存在(#2)。o2 = String.Intern(o2.ToString())
這樣的代碼,WriteLine返回的就是True。String.IsInterned
IsInterned,正如它的名字,判斷一個字符串是不是已經在內部池中。如果傳入的字符串已經在池中,則返回這個字符串對象的引用,如果不再池中,返回null。
下面是一個IsInterned例子:
string s = new string(new char[] {'x', 'y', 'z'}); Console.WriteLine(String.IsInterned(s) ?? "not interned"); String.Intern(s); Console.WriteLine(String.IsInterned(s) ?? "not interned"); Console.WriteLine(object.ReferenceEquals( String.IsInterned(new string(new char[] { 'x', 'y', 'z' })), s));
第一個WriteLine打印的是“not interned”,因為“xyz”還沒有存在于內部池中;第二個WriteLine打印了“xyz”因為現在內部池中有了“xyz”;第三個WriteLine打印True,因為對象引用的就是內部池中的“xyz”。
常量字符串自動被加入內部池
改變最后一行代碼為:
Console.WriteLine(object.ReferenceEquals("xyz", s));
你會發現,奇怪的事情發生了,這些代碼不再輸出“not interned”了,并且最后的兩個WriteLine輸出的是False!發生了什么?
原因就是這個最后添加的那行代碼中的常量“xyz”,CLR會將程序中使用的字符常量自動添加到內部池中。所以,當最后一行被添加之后,“xyz”在程序“運行之前”(避免嚴謹,這里用引號)就已經存在于內部池中。所以,當調用String.IsInterned的時候,返回的不再是null,而是指向“xyz”的引用。這也解釋了,為什么后面的ReferenceEquals返回False,因為s從來沒有被加到內部池中,其指向也不是內部池的"xyz"。
編譯器比你想象的要聰明
改變最后一行代碼為:
Console.WriteLine(object.ReferenceEquals("x" + "y" + "z", s));
運行一下,你會發現運行結果和直接使用“xyz”一樣。但這里使用了+運算符啊?編譯器不應該知道”x“+"y"+"z"最終的結果吧?
實際上,如果你將”x“+"y"+"z"替換為String.Format("{0}{1}{2}",'x','y','z'),結果確實就不一樣了。某種原因,CLR會將使用+運算符鏈接的字符串視為常量,而String.Format卻需要在運行時才能知道結果。為什么?看一下下面的代碼:
using System; class Program { public static void Main() { Console.WriteLine("x" + "y" + "z"); } }
這段代碼編譯之后,使用Ildasm.exe查看,會看到:
Screenshot - ILDasm intern-xyz Main method.png
看到了吧,編譯器足夠聰明,將”x“+"y"+"z"替換為”xyz“。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。