一、深入理解字符串的不可變特性
string可以看做是char的只讀數組。char c = s[1]
C#中字符串有一個重要的特性:不可變性,字符串一旦聲明就不再可以改變。所以只能通過索引來讀取指定位置的char,不能對指定位置的char進行修改。如果要對char進行修改,那么就必須創建一個新的字符串,用s. ToCharArray()方法得到字符串的char數組,對數組進行修改后,調用new string(char[])這個構造函數來創建char數組的字符串。一旦字符串被創建,那么char數組的修改也不會造成字符串的變化。
常見疑問:“誰說字符串不可變?string s1 = "abc";s1="123",s1這不是變了嗎”,要區分變量名和變量指向的值的區別:程序中可以有很多字符串,然后由字符串變量指向他們,變量可以指向其他的字符串,但是字符串本身沒有變化。字符串不可變性指的是內存中的字符串不可變,而不是變量不變。
string s2 = s1;//s2指向s1指向的字符串,而不是s2指向s1,哪怕s1以后指向了其他內存,那么s2還是指向"123"
內存分配分析:
“string s1 = "abc";”這句代碼執行完之后,在棧內存中開辟一塊內存空間存儲字符串變量s1,在堆內存中開辟一塊內存空間來存儲字符串對象“abc”,字符串變量s1指向字符串對象“abc”,如下圖所示:
s1="123";”這句代碼執行完之后,在堆內存中再開辟一塊內存空間來存儲字符串對象“123”,字符串變量s1指向字符串對象“123”,如下圖所示:
此時可以看到,字符串變量s1已經指向了新的字符串對象“123”了,字符串對象“abc”已經沒有任何的字符串變量指向它了,此時字符串對象“abc”就再也沒有用了,當對象一定不再有用的時候GC(垃圾收集器)就可以將對象回收了,所以GC會在一個合適的時機來回收字符串對象“abc”,釋放其占用的內存資源。判斷一個對象是否一定不再有用的標准就是沒有任何的變量指向它。
string s2 = s1; 這句代碼執行完之后,在棧內存中開辟一塊內存空間存儲字符串變量s2,字符串變量s2也指向字符串對象“123”
例子:將字符串中的l替換為i
1 string s = "hell world!";//字符數組 只讀的 不能對其 進行賦值 2 char[] chars = s.ToCharArray();//得到一個字符數組 3 chars[1]='i';//修改字符數組的值 4 s=new string(chars);//生成一個新的字符串 5 Console.WriteLine(s); 6 for (int i = 0; i < s.Length; i++) 7 { 8 Console.WriteLine(s[i]); 9 } 10 Console.ReadKey();
因為字符串是不可變的,所以CLR(CLR是公共語言運行時,Common Language Runtime)可能會將相同值的字符串用同一個實例。程序中大量使用字符串,有不少是重復性的,為了降低內存占用,.Net將代碼中聲明的字符串放到字符串拘留池中,值相同的字符串共享同一個實例。字符串是不變的。不是所有字符串都在拘留池中,.Net會判斷哪些該放。
object.ReferenceEquals方法判斷兩個變量是不是一個實例(同一個對象)
1 string s1 = "abc"; 2 string s2 = "abc"; 3 Console.WriteLine(object.ReferenceEquals(s1,s2));
動態字符串默認是不在字符串拘留池中的
字符串拘留池測試程序:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Drawing; 6 namespace 字符串拘留池 7 { 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 //重點1 13 //因為有字符串拘留池的存在所以下面的程序有一個string對象 14 //string s1 = "孤傲蒼狼"; 15 //string s2 = "孤傲蒼狼"; 16 string s1 = "孤傲蒼狼"; 17 string s2 = "孤傲蒼狼"; 18 if (s1 == s2)//這里比較的是s1和s2的內容是不是一樣 19 { 20 Console.WriteLine("s1==s2的結果是:{0}", s1 == s2); 21 } 22 //上面的代碼產生一個字符串對象 23 Console.WriteLine("object.ReferenceEquals(s1,s2)的結果是:{0}",object.ReferenceEquals(s1,s2));//這里判斷的是s1和s2所指向的對象是不是同一個 24 Console.WriteLine("================================================"); 25 string s3 = "yzk"; 26 string s4 = "yzk"; 27 string s5 = new string(s1.ToCharArray()); 28 //object.ReferenceEquals方法判斷兩個變量是不是一個實例(同一個對象) 29 Console.WriteLine("object.ReferenceEquals(s3, s4)的結果是:{0}",object.ReferenceEquals(s3, s4));//true 30 Console.WriteLine("object.ReferenceEquals(s3, s5)的結果是:{0}", object.ReferenceEquals(s3, s5));//false 31 Console.WriteLine("object.ReferenceEquals(s4, s5)的結果是:{0}", object.ReferenceEquals(s4, s5));//false 32 Console.WriteLine("================================================"); 33 string s6 = "abc"; 34 string s7 = "ab"+"c"; 35 Console.WriteLine("object.ReferenceEquals(s6, s7)的結果是:{0}", object.ReferenceEquals(s6, s7));//true 36 //默認只有代碼中寫的字面才會進入字符串拘留池,動態字符串默認是不在字符串拘留池中的 37 string str1 = "abc";//abc就是代碼中寫的字面 38 string a1 = Console.ReadLine(); 39 string a2 = Console.ReadLine(); 40 string str2 = a1 + a2;//str2是動態算出來的字符串, 41 Console.WriteLine("str1==str2的結果是:{0}",str1==str2);//結果為true 42 Console.WriteLine("object.ReferenceEquals(str1, str2)的結果是:{0}", object.ReferenceEquals(str1, str2));//結果為false 43 //重點2 44 //new就一定會創建一個新的對象 45 //兩個string對象 46 string str3 = new string("abc".ToCharArray()); 47 string str4 = "abc"; 48 //==是判斷內容是否相等 49 Console.WriteLine("str3==str4的結果是:{0}", str3 == str4);//結果為true 50 Console.WriteLine("object.ReferenceEquals(str3, str4)的結果是:{0}", object.ReferenceEquals(str3, str4));//結果為false 51 Console.WriteLine("================================================"); 52 Point p1 = new Point(3, 5); 53 Point p2 = new Point(3, 5); 54 //==是判斷內容是否相等 55 Console.WriteLine("p1 == p2的結果是:{0}",p1 == p2); 56 //object.ReferenceEquals判斷是否是同一個對象 57 Console.WriteLine("object.ReferenceEquals(p1, p2)的結果是:{0}", object.ReferenceEquals(p1, p2)); 58 Console.WriteLine("================================================"); 59 Console.ReadKey(); 60 } 61 } 62 }
程序運行結果:
string是不變的,因此每次運算都會重新創建一個string對象,例如:s=s+”abc”;,此時將會創建一個新的字符串對象,s指向了新創建的字符串對象
1 string s1=”a”; 2 string s2=”b”; 3 string s3=”c”; 4 string s4=s1+s2+s3;//s1+s2產生"ab","ab"+s3產生"abc",所以產生兩個字符串
大量的字符串相連會產生大量的中間字符串,字符串是對象,對象的產生是慢的,而且會占用大量的內存。所以要避免大量的字符串的連接操作。
例如:在for循環中拼接字符串
1 for(int i=1;i<=1000;i++) 2 { 3 s=s+i.ToString(); 4 }
每循環一次,就進行一次字符串拼接運算,那么就會創建一個新的字符串,循環1000次,就會創建1000個字符串,就需要開辟1000塊內存空間來存儲這些中間字符串,這是非常暫用內存資源的。
對象的創建是非常慢和消耗資源的,因此對於需要動態拼接字符串的場合(比如創建SQL語句)用StringBuilder來代替string,StringBuilder內部實現了字符串拼接不會有string的缺陷。