c#中String類型的存儲原理詳解
在我們正式了解c#中的String類型前,先來判斷一下下面代碼的結(jié)果吧~
String str1 = "123"; String str2 = str1; str2 = "321"; Console.WriteLine(str1);
上面代碼的最終輸出結(jié)果是123,如果有淺學過引用類型的同學一定會問:str2不是在存儲的是str1的引用么?那么str2不是和str1指向堆中同一塊內(nèi)存空間么?為什么在引用了str2使其改變數(shù)據(jù)后再打印出str1最終還是打印出來123?
這也是我最初的疑問,但不要著急,一步一步看下去,相信很快能了解清楚。
在正式開始之前,我們先了解一下c#中的內(nèi)存分區(qū):
內(nèi)存分區(qū)
- 棧區(qū):由編譯器自動分配釋放 ,存放值類型的對象本身,引用類型的引用地址(指針),靜態(tài)區(qū)對象的引用地址(指針),常量區(qū)對象的引用地址(指針)等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
- 堆區(qū)(托管堆):用于存放引用類型對象本身。在c#中由.net平臺的垃圾回收機制(GC)管理。棧,堆都屬于動態(tài)存儲區(qū),可以實現(xiàn)動態(tài)分配。
- (重點看)靜態(tài)區(qū)及常量區(qū):用于存放靜態(tài)類,靜態(tài)成員(靜態(tài)變量,靜態(tài)方法),常量的對象本身。由于存在棧內(nèi)的引用地址都在程序運行開始最先入棧,因此靜態(tài)區(qū)和常量區(qū)內(nèi)的對象的生命周期會持續(xù)到程序運行結(jié)束時,屆時靜態(tài)區(qū)內(nèi)和常量區(qū)內(nèi)對象才會被釋放和回收(編譯器自動釋放)。所以應(yīng)限制使用靜態(tài)類,靜態(tài)成員(靜態(tài)變量,靜態(tài)方法),常量,否則程序負荷高。
- 代碼區(qū):存放函數(shù)體內(nèi)的二進制代碼。
在c#中,String的存儲方式很特殊,在c#的內(nèi)存中,在常量區(qū)里會分配一塊空間叫做String暫存池(常量池),在某些時候,我們的字符串數(shù)據(jù)是存儲在這個常量池中的,而地址依然是存放在棧中。
例如用 String str = "xXXXX" 的方式來創(chuàng)建String變量的話,那么String的值便會存儲在String常量池中,在我們以這種方式創(chuàng)建String變量時,編譯器會先判斷你這個內(nèi)容有沒有已經(jīng)在常量池出現(xiàn)過了,如果已經(jīng)出現(xiàn)過,那么不會再在常量池中使用空間來存放一個相同的內(nèi)容,這個內(nèi)容只會固定有一個引用,所以在創(chuàng)造相同內(nèi)容的String的時候,他們的引用都是相同的。又有一種情況:一開始A和B內(nèi)容相同,就是說A與B的引用都相同時,此時將B的內(nèi)容更改,那么B的內(nèi)容在常量池中就會使用另一塊空間,那么相應(yīng)的B的引用也會改變,而A的引用并不會改變,因為A此時還是存儲的原來的內(nèi)容。我們可以來看簡易的圖解:
以上我們可以用代碼來證實我們的結(jié)論:
String str1 = "123"; String str2 = "123"; Console.WriteLine("此時還未將str1中的值做改變:"); if(object.ReferenceEquals(str1,str2)) { Console.WriteLine("此時引用相同"); } else { Console.WriteLine("此時引用不相同"); } if (object.ReferenceEquals(String.Intern(str1), String.Intern(str2))) { Console.WriteLine("此時存儲在同一塊常量池中,且引用相同"); } else { Console.WriteLine("此時兩字符串不相同,存在不同的空間中,且引用也不同"); } Console.WriteLine(); str1 = "12"; Console.WriteLine("此時將str1的值改變,比較str1與str2的引用和所指向的內(nèi)存空間是否相同:"); if (object.ReferenceEquals(str1, str2)) { Console.WriteLine("此時引用相同"); } else { Console.WriteLine("此時引用不相同"); } if (object.ReferenceEquals(String.Intern(str1), String.Intern(str2))) { Console.WriteLine("此時存儲在同一塊常量池中,且引用相同"); } else { Console.WriteLine("此時兩字符串不相同,存在不同的空間中,且引用也不同"); }
可以看到最終運行的結(jié)果:
為了更好理解以上代碼,下面是對代碼的一些東西的解釋:
object.ReferenceEquals
這個是用來比較兩個變量的引用是否一樣,如果一樣,那么則會返回true,否則將會返回false。
String.Intern
String.Intern的工作方式很好理解,你將一個字符串作為參數(shù)使用這個接口,如果這個字符串已經(jīng)存在池中,就返回這個存在的引用;如果不存在就將它加入到池中,并返回引用。
當然,以上只是針對用String str = "XXXXX";這樣創(chuàng)建變量的方式來討論的,那么什么時候創(chuàng)建String會考慮這樣的問題呢?下面來看情況總結(jié):
我們要知道不是所有字符串都放在常量池當中:
存放暫存池:
- 用字面量值創(chuàng)建String對象,例:String str = "ABCD";
- 用String.Intern(),例:StringBuilder sb = new StringBuilder(“ABCD”);string str1 = “ABCD”;string str2=string.Intern(sb.ToString);
- 字符串拼接,例:str1 = "ABCD";str2 = "EFG";str1+str2。
不存放暫存池(存放在堆中):
- 使用str.Tostring,例:str1 = "ABCD";str2 = str1.ToString();
- 使用char[].Tostring(),例:str1=ABCD”; char[]charArray = str1.ToArray(); str2 = charArray.ToString();
- 使用new String(),例:
str1=”999”;char[] charArray = str1.ToArray();string str2 = new string(charArray);string str3 = new string(charArray); char[] charArray = {‘A','B'};str1 = “ABCDE”;str2 =”CDE”+charArray.Tostring(); char[] charArray1 = {‘A','B'};char charArray2 = {‘C','D','E'}; str1 =”ABCDE”;str2=charArray1.ToString()+charArray2.ToString();
到此這篇關(guān)于c#中String類型的存儲原理詳解的文章就介紹到這了,更多相關(guān)c# String類型存儲內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C# IP地址與整數(shù)之間轉(zhuǎn)換的具體方法
這篇文章介紹了C# IP地址與整數(shù)之間轉(zhuǎn)換的具體方法,有需要的朋友可以參考一下2013-10-10C# InitializeComponent()方法案例詳解
這篇文章主要介紹了C# InitializeComponent()方法案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08