首頁 > 軟體

c#中String型別的儲存原理詳解

2022-04-01 19:01:16

在我們正式瞭解c#中的String型別前,先來判斷一下下面程式碼的結果吧~

String str1 = "123";
String str2 = str1;
str2 = "321";
Console.WriteLine(str1);

上面程式碼的最終輸出結果是123,如果有淺學過參照型別的同學一定會問:str2不是在儲存的是str1的參照麼?那麼str2不是和str1指向堆中同一塊記憶體空間麼?為什麼在參照了str2使其改變資料後再列印出str1最終還是列印出來123?

這也是我最初的疑問,但不要著急,一步一步看下去,相信很快能瞭解清楚。

在正式開始之前,我們先了解一下c#中的記憶體分割區:

記憶體分割區

  • 棧區:由編譯器自動分配釋放 ,存放值型別的物件本身,參照型別的參照地址(指標),靜態區物件的參照地址(指標),常數區物件的參照地址(指標)等。其操作方式類似於資料結構中的棧。
  • 堆區(託管堆):用於存放參照型別物件本身。在c#中由.net平臺的垃圾回收機制(GC)管理。棧,堆都屬於動態儲存區,可以實現動態分配。
  • (重點看)靜態區及常數區:用於存放靜態類,靜態成員(靜態變數,靜態方法),常數的物件本身。由於存在棧內的參照地址都在程式執行開始最先入棧,因此靜態區和常數區內的物件的生命週期會持續到程式執行結束時,屆時靜態區內和常數區內物件才會被釋放和回收(編譯器自動釋放)。所以應限制使用靜態類,靜態成員(靜態變數,靜態方法),常數,否則程式負荷高。
  • 程式碼區:存放函數體內的二進位制程式碼。

在c#中,String的儲存方式很特殊,在c#的記憶體中,在常數區裡會分配一塊空間叫做String暫存池(常數池),在某些時候,我們的字串資料是儲存在這個常數池中的,而地址依然是存放在棧中。

例如用 String str = "xXXXX" 的方式來建立String變數的話,那麼String的值便會儲存在String常數池中,在我們以這種方式建立String變數時,編譯器會先判斷你這個內容有沒有已經在常數池出現過了,如果已經出現過,那麼不會再在常數池中使用空間來存放一個相同的內容,這個內容只會固定有一個參照,所以在創造相同內容的String的時候,他們的參照都是相同的。又有一種情況:一開始A和B內容相同,就是說A與B的參照都相同時,此時將B的內容更改,那麼B的內容在常數池中就會使用另一塊空間,那麼相應的B的參照也會改變,而A的參照並不會改變,因為A此時還是儲存的原來的內容。我們可以來看簡易的圖解:

以上我們可以用程式碼來證實我們的結論:

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的參照和所指向的記憶體空間是否相同:");
 
            if (object.ReferenceEquals(str1, str2))
            {
                Console.WriteLine("此時參照相同");
            }
            else
            {
                Console.WriteLine("此時參照不相同");
            }
            if (object.ReferenceEquals(String.Intern(str1), String.Intern(str2)))
            {
                Console.WriteLine("此時儲存在同一塊常數池中,且參照相同");
            }
            else
            {
                Console.WriteLine("此時兩字串不相同,存在不同的空間中,且參照也不同");
            }

可以看到最終執行的結果:

為了更好理解以上程式碼,下面是對程式碼的一些東西的解釋:

object.ReferenceEquals

這個是用來比較兩個變數的參照是否一樣,如果一樣,那麼則會返回true,否則將會返回false。

String.Intern

String.Intern的工作方式很好理解,你將一個字串作為引數使用這個介面,如果這個字串已經存在池中,就返回這個存在的參照;如果不存在就將它加入到池中,並返回參照。

 當然,以上只是針對用String str = "XXXXX";這樣建立變數的方式來討論的,那麼什麼時候建立String會考慮這樣的問題呢?下面來看情況總結:

我們要知道不是所有字串都放在常數池當中:

存放暫存池:

  • 用字面量值建立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();

到此這篇關於c#中String型別的儲存原理詳解的文章就介紹到這了,更多相關c# String型別儲存內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com