淺談C#中的string駐留池
昨天看群里在討論C#中的string駐留池,炒的火熱,幾輪下來理論一堆堆,但是在證據(jù)提供上都比較尷尬。雖然這東西很基礎(chǔ),但比較好的回答也不是那么容易,這篇我就以我能力范圍之內(nèi)跟大家分享一下
一:無處不在的池
開發(fā)這么多年,相信大家對(duì)‘池' 這個(gè)概念都耳熟能詳了,連接池,線程池,對(duì)象池,還有這里的駐留池,池的存在就是為了復(fù)用為了共享,獨(dú)樂樂不如眾樂樂,畢竟一個(gè)字符串的生成和銷毀既浪費(fèi)空間又浪費(fèi)時(shí)間,還不如先養(yǎng)著。
1. 說說現(xiàn)象
通常我們臆想中是這么認(rèn)為的,定義幾個(gè)字符串變量,堆上就會(huì)分配幾個(gè)string對(duì)象,其實(shí)這底層有一種叫駐留池技術(shù)可以做到如果兩個(gè)字符串內(nèi)容相同,那就在堆上只分配一個(gè)string對(duì)象,然后將引用地址分配給兩個(gè)字符串變量,這樣就可以大大降低了內(nèi)存使用,如果用代碼表示就是下面這樣。
public static void Main(string[] args) { var str1 = "nihao"; var str2 = "nihao"; var b = string.ReferenceEquals(str1, str2); Console.WriteLine(b); } ----------- output ----------- True
2. 實(shí)現(xiàn)原理
那怎么做到的呢? 其實(shí)CLR在運(yùn)行時(shí)調(diào)用JIT把你的MSIL代碼轉(zhuǎn)成機(jī)器代碼的時(shí)候會(huì)發(fā)現(xiàn)你的元數(shù)據(jù)中定義了相同內(nèi)容的字符串對(duì)象,CLR就會(huì)把你的字符串放入它私有的的內(nèi)部字典中,其中key就是字符串內(nèi)容,value就是分配在堆上的字符串引用地址,這個(gè)字典就是所謂的駐留池,如果不是很明白,我來畫一張圖。
3. windbg驗(yàn)證
可以用windbg看一下棧中的str1和str2是否都指向了堆上對(duì)象的地址。
~0s -> !clrstack -l 在主線程的線程棧上找到變量str1和str2
0:000> ~0s ntdll!ZwReadFile+0x14: 00007ff8`fea4aa64 c3 ret 0:000> !clrstack -l OS Thread Id: 0x1c1c (0) Child SP IP Call Site 000000ac0b7fed00 00007ff889e608e9 *** WARNING: Unable to verify checksum for ConsoleApp2.exe ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 30] LOCALS: 0x000000ac0b7fed38 = 0x0000024a21f22d48 0x000000ac0b7fed30 = 0x0000024a21f22d48 000000ac0b7fef48 00007ff8e9396c93 [GCFrame: 000000ac0b7fef48]
從上面代碼的 LOCALS 的 0x000000ac0b7fed38 = 0x0000024a21f22d48
和 0x000000ac0b7fed30 = 0x0000024a21f22d48
可以看到兩個(gè)局部變量的引用地址都是 0x0000024a21f22d48
,說明指向的都是一個(gè)堆對(duì)象,接下來再把堆上的內(nèi)容打出來。
0:000> !do 0x0000024a21f22d48 Name: System.String MethodTable: 00007ff8e7a959c0 EEClass: 00007ff8e7a72ec0 Size: 36(0x24) bytes File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll String: nihao Fields: MT Field Offset Type VT Attr Value Name 00007ff8e7a985a0 4000281 8 System.Int32 1 instance 5 m_stringLength 00007ff8e7a96838 4000282 c System.Char 1 instance 6e m_firstChar 00007ff8e7a959c0 4000286 d8 System.String 0 shared static Empty >> Domain:Value 0000024a203d41c0:NotInit <<
可以看到,果然是System.String對(duì)象,這就和我的圖是相符的。
二 駐留池的驗(yàn)證
1. String下的駐留池驗(yàn)證方法
很遺憾的是水平有限,由于駐留池既不在堆中也不在棧上,目前還不知道怎么用windbg去打印CLR中駐留池字典內(nèi)容,不過也可以通過 string.Intern 去驗(yàn)證。
// // Summary: // Retrieves the system's reference to the specified System.String. // // Parameters: // str: // A string to search for in the intern pool. // // Returns: // The system's reference to str, if it is interned; otherwise, a new reference // to a string with the value of str. // // Exceptions: // T:System.ArgumentNullException: // str is null. [SecuritySafeCritical] public static String Intern(String str);
從注釋中可以看到,這個(gè)方法的意思就是:如果你定義的str在駐留池中存在,那么就返回駐留池中命中內(nèi)容的堆上引用地址,如果不存在,將新字符串插入駐留池中再返回堆上引用,先上一下代碼:
public static void Main(string[] args) { var str1 = "nihao"; var str2 = "nihao"; //驗(yàn)證nihao是否在駐留池中,如果存在那么str3 和 str1,str2一樣的引用 var str3 = string.Intern("nihao"); //驗(yàn)證新的字符串內(nèi)容是否進(jìn)入駐留池中 var str4 = string.Intern("cnblogs"); var str5 = string.Intern("cnblogs"); Console.ReadLine(); }
接下來分別驗(yàn)證一下str3是否也是和str1和str2一樣的引用,以及str5是否存在駐留池中。
ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 37] LOCALS: 0x00000047105fea58 = 0x0000018537312d48 0x00000047105fea50 = 0x0000018537312d48 0x00000047105fea48 = 0x0000018537312d48 0x00000047105fea40 = 0x0000018537312d70 0x00000047105fea38 = 0x0000018537312d70
從五個(gè)變量地址中可以看到,nihao已經(jīng)被str1,str2,str3共享,cnblogs也進(jìn)入了駐留池中實(shí)現(xiàn)了共享。
2. 運(yùn)行期相同string是否進(jìn)入駐留池
這里面有一個(gè)坑,前面討論的相同字符串都是在編譯期就知道的,但運(yùn)行時(shí)中的相同字符串是否也會(huì)進(jìn)入駐留池呢? 這是一個(gè)讓人充滿好奇的話題,可以試一下,在程序運(yùn)行時(shí)接受IO輸入內(nèi)容hello,看看是否和str1,str2共享引用地址。
public static void Main(string[] args) { var str1 = "nihao"; var str2 = "nihao"; var str3 = Console.ReadLine(); Console.WriteLine("輸入完成!"); Console.ReadLine(); } 0:000> !clrstack -l 000000f6d35fee50 00007ff889e7090d *** WARNING: Unable to verify checksum for ConsoleApp2.exe ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 33] LOCALS: 0x000000f6d35fee98 = 0x000002cb1a552d48 0x000000f6d35fee90 = 0x000002cb1a552d48 0x000000f6d35fee88 = 0x000002cb1a555f28 0:000> !do 0x000002cb1a555f28 Name: System.String MethodTable: 00007ff8e7a959c0 EEClass: 00007ff8e7a72ec0 Size: 36(0x24) bytes File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll String: nihao Fields: MT Field Offset Type VT Attr Value Name 00007ff8e7a985a0 4000281 8 System.Int32 1 instance 5 m_stringLength 00007ff8e7a96838 4000282 c System.Char 1 instance 6e m_firstChar 00007ff8e7a959c0 4000286 d8 System.String 0 shared static Empty >> Domain:Value 000002cb18ad39f0:NotInit <<
從上面內(nèi)容可以看到,從Console.ReadLine接收到的引用地址是 0x000002cb1a555f28 ,雖然是相同內(nèi)容,但卻沒有使用駐留池,這是因?yàn)轳v留池在JIT靜態(tài)解析期就已經(jīng)解析完成了,也就無法享受復(fù)用之優(yōu),如果還想復(fù)用的話,在 Console.ReadLine() 包一層string.Intern即可,如下所示:
public static void Main(string[] args) { var str1 = "nihao"; var str2 = "nihao"; var str3 = string.Intern(Console.ReadLine()); Console.WriteLine("輸入完成!"); Console.ReadLine(); } ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 33] LOCALS: 0x0000008fac1fe9c8 = 0x000001ff46582d48 0x0000008fac1fe9c0 = 0x000001ff46582d48 0x0000008fac1fe9b8 = 0x000001ff46582d48
可以看到這個(gè)時(shí)候str1,str2,str3共享一個(gè)內(nèi)存地址 0x000001ff46582d48
。
四: 總結(jié)
駐留池技術(shù)是個(gè)很🐮👃的東西,很好的解決字符串在堆上的重復(fù)分配問題,大大減小了堆的內(nèi)存占用,但也要明白運(yùn)行期的IO輸入無法共享駐留池的解決方案。
好了,本篇就說到這里,希望對(duì)你有幫助!
以上就是淺談C#中的string駐留池的詳細(xì)內(nèi)容,更多關(guān)于C# string駐留池的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- C# GUID ToString的使用總結(jié)
- C#開發(fā)之int與string轉(zhuǎn)化操作
- C#中的char、string和StringBuilder的使用詳解
- c# String擴(kuò)展 讓你在PadLeft和PadRight時(shí)不再受單雙字節(jié)問題困擾
- 淺談C# StringBuilder內(nèi)存碎片對(duì)性能的影響
- C# 用什么方法將BitConverter.ToString產(chǎn)生字符串再轉(zhuǎn)換回去
- C#實(shí)現(xiàn)String字符串轉(zhuǎn)化為SQL語句中的In后接的參數(shù)詳解
- 【C#基礎(chǔ)】Substring截取字符串的方法小結(jié)(推薦)
- C# String常用函數(shù)的使用詳解
相關(guān)文章
unity3D實(shí)現(xiàn)三維物體跟隨鼠標(biāo)
這篇文章主要為大家詳細(xì)介紹了unity3D實(shí)現(xiàn)三維物體跟隨鼠標(biāo),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12C#中l(wèi)abel內(nèi)容顯示不全、不完整的解決方法
這篇文章主要介紹了C#中l(wèi)abel內(nèi)容顯示不全、不完整的解決方法,只需要把兩個(gè)屬性設(shè)置一下即可解決這個(gè)問題,需要的朋友可以參考下2015-06-06C#導(dǎo)出pdf的實(shí)現(xiàn)方法(瀏覽器不預(yù)覽直接下載)
這篇文章主要給大家介紹了關(guān)于C#導(dǎo)出pdf的實(shí)現(xiàn)方法,實(shí)現(xiàn)后瀏覽器不預(yù)覽就可以直接下載,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用C#具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12C#中的char、string和StringBuilder的使用詳解
這篇文章主要介紹了C#中的char、string和StringBuilder的使用詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07Unity3D實(shí)戰(zhàn)之答題系統(tǒng)的實(shí)現(xiàn)
本文將用Unity3D制作一個(gè)答題系統(tǒng),可以從文本文檔中提取題目和分?jǐn)?shù),然后綁定到UI上,在答題的過程中,自動(dòng)判斷分?jǐn)?shù),自動(dòng)判斷正確率。感興趣的可以學(xué)習(xí)一下2022-03-03DevExpress之ChartControl實(shí)現(xiàn)時(shí)間軸實(shí)例
這篇文章主要介紹了DevExpress中ChartControl實(shí)現(xiàn)時(shí)間軸的方法,涉及相關(guān)C#繪圖程序用法,具有一定的實(shí)用價(jià)值,需要的朋友可以參考下2014-10-10