C#中字符串優(yōu)化String.Intern、IsInterned詳解
前言
string是一種很特殊的數(shù)據(jù)類型,它既是基元類型又是引用類型,在編譯以及運(yùn)行時(shí),.Net都對(duì)它做了一些優(yōu)化工作,正式這些優(yōu)化工作有時(shí)會(huì)迷惑編程人員,使string看起來難以琢磨。本文將給大家詳細(xì)介紹關(guān)于C#字符串優(yōu)化String.Intern、IsInterned的相關(guān)內(nèi)容,分享出來供大家參考學(xué)習(xí),下面話不多說了,來一起看看詳細(xì)的介紹吧。
首先看一段程序:
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)); } }
這個(gè)沒有什么特殊的地方,相信大家都知道運(yùn)行結(jié)果:
hello, hello world False False
第二個(gè)WriteLine使用==比較兩個(gè)字符串,返回False是因?yàn)樗麄儾灰恢隆6詈笠粋€(gè)WriteLine返回False,因?yàn)閍、b的引用不一致。
接下來,我們?cè)诖a的最后添加代碼:
Console.WriteLine((a + " world") == b); Console.WriteLine(object.ReferenceEquals((a + " world"), b));
這個(gè)的輸出,相信也不會(huì)出乎大家的意料。前者返回True,因?yàn)?=兩邊的內(nèi)容相等;后者為False,因?yàn)?運(yùn)算符執(zhí)行完畢后,會(huì)創(chuàng)建一個(gè)新的string實(shí)例,這個(gè)實(shí)例與b的引用不一致。
上面這些就是對(duì)象的通常工作方式,兩個(gè)獨(dú)立的對(duì)象可以擁有同樣的內(nèi)容,但他們卻是不同的個(gè)體。
接下來,我們就來說一下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)); } }
運(yùn)行一下,結(jié)果為:
hello world, hello world: True, False
再一次,沒什么意外,==返回true因?yàn)樗麄儍?nèi)容相同,ReferenceEquals返回False因?yàn)樗麄兪遣煌囊谩?br /> 現(xiàn)在在后面添加這樣的代碼:
helloWorld2 = "hello world"; Console.WriteLine("{0}, {1}: {2}, {3}", helloWorld, helloWorld2, helloWorld == helloWorld2, object.ReferenceEquals(helloWorld, helloWorld2));
運(yùn)行,結(jié)果為:
hello world, hello world: True, True
等一下,這里的hellowWorld與helloWorld2引用一致?這個(gè)結(jié)果,相信很多人都有些接受不了。這里的helloWorld2與上面的hello + " world"應(yīng)該是一樣的,但為什么ReferenceEquals返回的是True?
String.Intern
有經(jīng)驗(yàn)的程序員們,應(yīng)該知道,一個(gè)大型項(xiàng)目中,字符串的數(shù)量是巨大的。有些時(shí)候會(huì)出現(xiàn)幾百、幾千、甚至幾萬的重復(fù)字符串存在。這些字符串的內(nèi)容相同,但卻會(huì)重復(fù)分配內(nèi)存,占用巨額的存儲(chǔ)空間,這個(gè)肯定是要優(yōu)化處理的。而C#在處理這個(gè)問題的時(shí)候,采用的就是普遍的做法,建立內(nèi)部的池,池中每一個(gè)不同的字符串存在唯一一個(gè)個(gè)體在池中(這個(gè)方案在各種大型項(xiàng)目中都能見得到)。而C#畢竟是一種語言,而不是一個(gè)面向某個(gè)具體領(lǐng)域的技術(shù),所以,它不能將這種內(nèi)部的池技術(shù),做成全部自動(dòng)化的。因?yàn)槲覀儾恢?,將來C#會(huì)被使用到何種規(guī)模的項(xiàng)目中。如果完全自動(dòng)化維護(hù)這個(gè)內(nèi)部池,可能會(huì)在大型項(xiàng)目中,造成內(nèi)存的巨大浪費(fèi),畢竟不是所有的字符串都有必要加到這個(gè)常駐的池中的。于是,C#提供了String.Intern和String.IsInterned接口,交給程序員自己維護(hù)內(nèi)部的池。
String.Intern的工作方式很好理解,你將一個(gè)字符串作為參數(shù)使用這個(gè)接口,如果這個(gè)字符串已經(jīng)存在池中,就返回這個(gè)存在的引用;如果不存在就將它加入到池中,并返回引用,例如:
Console.WriteLine(object.ReferenceEquals(String.Intern(helloWorld), String.Intern(helloWorld2)));
這段代碼將返回True,盡管helloWorld與helloWorld2的引用不同,但他們的內(nèi)容相同。
這里我們花幾分鐘,測(cè)試一下String.Intern,因?yàn)樵谀承┣闆r下,它產(chǎn)生的結(jié)果,有點(diǎn)違反直覺。這里是一個(gè)例子:
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)));
第一個(gè)WriteLine返回False很好理解,因?yàn)镾tring.Copy創(chuàng)建了一個(gè)a的新的實(shí)例,所以,o與a的引用不用。
但為什么第二個(gè)WriteLine返回的是True?思考一下吧,下面再看一個(gè)例子:
object o2 = String.Copy(a); String.Intern(o2.ToString()); Console.WriteLine(object.ReferenceEquals(o2, String.Intern(a)));
這個(gè)看起來,與上面的做了同樣的事,但為什么WriteLine返回的是False?
首先,需要說明一下ToString的工作方式,它總是返回它自身的引用。o是一個(gè)指向“abc”的變量,調(diào)用ToString返回的就是這個(gè)引用。所以,對(duì)于上面的內(nèi)容,可以這樣解釋:
- 開始,變量a指向字符串對(duì)象“abc”(#1),變量o指向另一個(gè)字符串對(duì)象(#2),也包含“abc”。
- 調(diào)用
String.Intern(o.ToString())
將對(duì)象#2的引用添加到內(nèi)部池中。 - 現(xiàn)在#2對(duì)象已經(jīng)存在池中了,任何時(shí)候,使用“abc”調(diào)用String.Intern都將返回#2的引用(o指向了這個(gè)對(duì)象)。
- 所以,當(dāng)你使用ReferenceEquals比較o和String.Intern(a)時(shí),返回True。因?yàn)?code>String.Intern(a)返回的是#2的引用。
- 現(xiàn)在我們創(chuàng)建一個(gè)新的變量o2,使用String.Copy(a)創(chuàng)建一個(gè)新的對(duì)象#3,它也包含“abc”。
- 調(diào)用
String.Intern(o2.ToString())
沒有向內(nèi)部池中添加任何內(nèi)容,因?yàn)椤癮bc”已經(jīng)存在(#2)。 - 所以,此時(shí)調(diào)用Intern返回的是對(duì)象#2的引用。注意,這里并沒有使用類似o2 = String.Intern(o2.ToString())這樣的代碼。
- 這就是為什么最后一行WriteLine打印的False的原因,因?yàn)槲覀冊(cè)趪L試比較#3與#2的引用。如果如7中所說,添加
o2 = String.Intern(o2.ToString())
這樣的代碼,WriteLine返回的就是True。
String.IsInterned
IsInterned,正如它的名字,判斷一個(gè)字符串是不是已經(jīng)在內(nèi)部池中。如果傳入的字符串已經(jīng)在池中,則返回這個(gè)字符串對(duì)象的引用,如果不再池中,返回null。
下面是一個(gè)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));
第一個(gè)WriteLine打印的是“not interned”,因?yàn)椤皒yz”還沒有存在于內(nèi)部池中;第二個(gè)WriteLine打印了“xyz”因?yàn)楝F(xiàn)在內(nèi)部池中有了“xyz”;第三個(gè)WriteLine打印True,因?yàn)閷?duì)象引用的就是內(nèi)部池中的“xyz”。
常量字符串自動(dòng)被加入內(nèi)部池
改變最后一行代碼為:
Console.WriteLine(object.ReferenceEquals("xyz", s));
你會(huì)發(fā)現(xiàn),奇怪的事情發(fā)生了,這些代碼不再輸出“not interned”了,并且最后的兩個(gè)WriteLine輸出的是False!發(fā)生了什么?
原因就是這個(gè)最后添加的那行代碼中的常量“xyz”,CLR會(huì)將程序中使用的字符常量自動(dòng)添加到內(nèi)部池中。所以,當(dāng)最后一行被添加之后,“xyz”在程序“運(yùn)行之前”(避免嚴(yán)謹(jǐn),這里用引號(hào))就已經(jīng)存在于內(nèi)部池中。所以,當(dāng)調(diào)用String.IsInterned的時(shí)候,返回的不再是null,而是指向“xyz”的引用。這也解釋了,為什么后面的ReferenceEquals返回False,因?yàn)閟從來沒有被加到內(nèi)部池中,其指向也不是內(nèi)部池的"xyz"。
編譯器比你想象的要聰明
改變最后一行代碼為:
Console.WriteLine(object.ReferenceEquals("x" + "y" + "z", s));
運(yùn)行一下,你會(huì)發(fā)現(xiàn)運(yùn)行結(jié)果和直接使用“xyz”一樣。但這里使用了+運(yùn)算符啊?編譯器不應(yīng)該知道”x“+"y"+"z"最終的結(jié)果吧?
實(shí)際上,如果你將”x“+"y"+"z"替換為String.Format("{0}{1}{2}",'x','y','z'),結(jié)果確實(shí)就不一樣了。某種原因,CLR會(huì)將使用+運(yùn)算符鏈接的字符串視為常量,而String.Format卻需要在運(yùn)行時(shí)才能知道結(jié)果。為什么?看一下下面的代碼:
using System; class Program { public static void Main() { Console.WriteLine("x" + "y" + "z"); } }
這段代碼編譯之后,使用Ildasm.exe查看,會(huì)看到:
Screenshot - ILDasm intern-xyz Main method.png
看到了吧,編譯器足夠聰明,將”x“+"y"+"z"替換為”xyz“。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- C#與C++?dll之間傳遞字符串string?wchar_t*?char*?IntPtr問題
- C#?BitArray(點(diǎn)矩陣)轉(zhuǎn)換成int和string的方法實(shí)現(xiàn)
- C#開發(fā)之int與string轉(zhuǎn)化操作
- C#中BitConverter.ToUInt16()和BitConverter.ToString()的簡(jiǎn)單使用
- C# 6.0 內(nèi)插字符串(Interpolated Strings )的使用方法
- C#中把字符串String轉(zhuǎn)換為整型Int的小例子
- c# StringBuilder.Replace 方法 (Char, Char, Int32, Int32)
- C#中String轉(zhuǎn)int的四種方法
相關(guān)文章
C# 開發(fā)圓角控件(窗體)的具體實(shí)現(xiàn)
這篇文章主要介紹了C# 開發(fā)圓角控件的具體實(shí)現(xiàn),需要的朋友可以參考下2014-02-02C#關(guān)于System.Collections空間詳解
這篇文章主要介紹了C#關(guān)于System.Collections空間,需要的朋友可以參考下2014-07-07C#利用Windows自帶gdi32.dll實(shí)現(xiàn)抓取屏幕功能實(shí)例
這篇文章主要介紹了C#利用Windows自帶gdi32.dll實(shí)現(xiàn)抓取屏幕功能,是C#程序設(shè)計(jì)中常見的一個(gè)重要技巧,需要的朋友可以參考下2014-08-08C#實(shí)現(xiàn)在listview中插入圖片實(shí)例代碼
這篇文章主要介紹了C#實(shí)現(xiàn)在listview中插入圖片實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-03-03