今天下午朋友討論組上討論一個關於string的問題,問題是這樣的,string a="aaa";string b=a;a="bbb",為什么測試b的值不改變?之前我看過一個文章,知道肯定不相等,因為引用地址的一系列問題,但是不能很好的解釋於同事聽,所以幾經查閱資料,在博客園里找到一篇文章,解決了我的疑問,同時也解決了關於C#中"=="與equals的計算結果與別的語言不一致的問題。在此轉載過來,以備鞏固。(以下為轉載內容,轉載地址http://terrylee.cnblogs.com/archive/2005/12/26/304876.html)
概述
String在任何語言中,都有它的特殊性,在.NET中也是如此。它屬於基本數據類型,也是基本數據類型中唯一的引用類型。字符串可以聲明為常量,但是它卻放在了堆中。希望通過本文能夠使大家對.NET中的String有一個深入的了解。
不可改變對象
在.NET中String是不可改變對象,一旦創建了一個String對象並為它賦值,它就不可能再改變,也就是你不可能改變一個字符串的值。這句話初聽起來似乎有些不可思議,大家也許馬上會想到字符串的連接操作,我們不也可以改變字符串嗎?看下面這段代碼:
using System; namespace Demo1 { /// <summary> /// String連接測試 /// </summary> public class Test { public static void Main(string[] args) { string a = "1234"; Console.WriteLine(a); a += "5678"; Console.WriteLine(a); Console.ReadLine(); } } }
運行的結果:
1234
12345678
看起來我們似乎已經把MyStr的值從“1234”改為了“12345678”。事實是這樣的嗎?實際上並沒有改變。在第5行代碼中創建了一個String對象它的值是“1234”,MyStr指向了它在內存中的地址;第七行代碼中創建了一個新的String對象它的值是“12345678”,MyStr指向了新的內存地址。這時在堆中其實存在着兩個字符串對象,盡管我們只引用了它們中的一個,但是字符串“1234”仍然在內存中駐留。
引用類型
前面說過String是引用類型,這就是如果我們創建很多個相同值的字符串對象,它在內存中的指向地址應該是一樣的。也就是說,當我們創建了字符串對象a,它的值是“1234”,當我們再創建一個值為“1234”的字符串對象b時它不會再去分配一塊內存空間,而是直接指向了a在內存中的地址。這樣可以確保內存的有效利用。看下面的代碼:
using System; namespace Demo2 { /// <summary> /// String引用類型測試 /// </summary> public class Test { public static void Main(string[] args) { string a = "1234"; Console.WriteLine(a); Test.Change(a); Console.WriteLine(a); Console.ReadLine(); } public static void Change(string s) { s = "5678"; } } }
運行結果:
1234
1234
做一個小改動,注意Change(ref string s)
using System; namespace Demo2 { /// <summary> /// String引用類型測試 /// </summary> public class Test { public static void Main(string[] args) { string a = "1234"; Console.WriteLine(a); Test.Change(ref a); Console.WriteLine(a); Console.ReadLine(); } public static void Change(ref string s) { s = "5678"; } } }
運行結果:
1234
5678
字符串的比較
在.NET中,對字符串的比較操作並不僅僅是簡單的比較二者的值,= =操作首先比較兩個字符串的引用,如果引用相同,就直接返回True;如果不同再去比較它們的值。所以如果兩個值相同的字符串的比較相對於引用相同的字符串的比較要慢,中間多了一步判斷引用是否相同。看下面這段代碼:
using System; namespace Demo3 { /// <summary> /// String類型的比較 /// </summary> public class Test { public static void Main(string[] args) { string a = "1234"; string b = "1234"; string c = "123"; c += "4"; int times = 1000000000; int start,end; ///測試引用相同所用的實際時間 start = Environment.TickCount; for(int i=0;i<times;i++) { if(a==b) {} } end = Environment.TickCount; Console.WriteLine((end-start)); ///測試引用不同而值相同所用的實際時間 start = Environment.TickCount; for(int i=0;i<times;i++) { if(a==c) {} } end = Environment.TickCount; Console.WriteLine((end-start)); Console.ReadLine(); } } }
執行的結果(運行的結果可能有些不同):
1671
4172
由此我們看出值相同時的比較用= =比引用相同時的比較慢了好多。這里僅僅是一個測試,因為做這樣的比較並沒有任何實際的意義。
有一點需要明確的是,.NET中==跟Equals()內部機制完全是一樣的,==是它的一個重載。
public static bool operator ==(string a, string b) { return string.Equals(a, b); } public static bool Equals(string a, string b) { if (a == b) { return true; } if ((a != null) && (b != null)) { return a.Equals(b); } return false; }
字符串駐留
看一下這段代碼:
using System; namespace Demo4 { /// <summary> /// String的駐留 /// </summary> public class Test { public static void Main(string[] args) { string a = "1234"; string s = "123"; s += "4"; string b = s; string c = String.Intern(s); Console.WriteLine((object)a == (object)b); Console.WriteLine((object)a == (object)c); Console.ReadLine(); } } }
執行的結果:
False
True
在這段代碼中,比較這兩個對象發現它的引用並不是一樣的。如果要想是它們的引用相同,可以用Intern()函數來進行字符串的駐留(如果有這樣的值存在)。
StringBuilder對象
通過上面的分析可以看出,String類型在做字符串的連接操作時,效率是相當低的,並且由於每做一個連接操作,都會在內存中創建一個新的對象,占用了大量的內存空間。這樣就引出StringBuilder對象,StringBuilder對象在做字符串連接操作時是在原來的字符串上進行修改,改善了性能。這一點我們平時使用中也許都知道,連接操作頻繁的時候,使用StringBuilder對象。但是這兩者之間的差別到底有多大呢?來做一個測試:
using System; using System.Text; namespace Demo5 { /// <summary> /// String和StringBulider比較 /// </summary> public class Test { public static void Main(string[] args) { string a = ""; StringBuilder s = new StringBuilder(); int times = 10000; int start,end; ///測試String所用的時間 start = Environment.TickCount; for(int i=0;i<times;i++) { a += i.ToString(); } end = Environment.TickCount; Console.WriteLine((end-start)); ///測試StringBuilder所用的時間 start = Environment.TickCount; for(int i=0;i<times;i++) { s.Append(i.ToString()); } end = Environment.TickCount; Console.WriteLine((end-start)); Console.ReadLine(); } } }
運行結果:
884
0
通過上面的分析,可以看出用String來做字符串的連接時效率非常低,但並不是所任何情況下都要用StringBuilder,當我們連接很少的字符串時可以用String,但當做大量的或頻繁的字符串連接操作時,就一定要用StringBuilder。