String類型在MSDN中的描述如下:
String is called immutable because its value cannot be modified once it has been created. Methods that appear to modify a String actually return a new String containing the modification. If it is necessary to modify the actual contents of a string-like object, use the System.Text.StringBuilder class.
基本意思是:
String對象是一成不變的,一旦創建其值便無法修改。任何對String對象的修改其實是返回包含修改的新的String對象。如果有必要像修改對象一樣修改的字符串的實際內容,建議使用System.Text.StringBuilder
也就是說,如果我們要改變一個字符串裏的內容,其它是先創建一個新的字符串(當然要分配新的內存),然後把要修改的內容存入新地址,然後返回新內存地址。
而System.Text.StringBuilder則不用新創建新對象,這樣,System.Text.StringBuilder用於連接字符的速度就遠比String要快的多。
關於C#語言中String類型和StringBuilder類型之間的差別,互聯網上有很多種解釋,不過其中大多數關注的只是他們在使用時時間複雜度上的差別,其實在空間複雜度上,他們之間的差距也是巨大的。
實驗說明:1.該實驗是在控制檯應用程序下實施的
2.通過多次修改一個StringBuilder或String變量來監測它們消耗的時間和內存
在空間複雜度的監測上,我主要是監測他們在內存消耗上的差別(另外它們佔用的交換文件大小也不一樣)。要監測內存的改變,這裏就需要用到API函數來獲取系統信息。所以在調用API函數之前,必須要先導入System.Runtime.InteropServices這個命名空間,然後在創建一個用來獲取內存信息的類:
public class MemoryInfo
{
[DllImport("kernel32")]
public static extern void GlobalMemoryStatus(ref MEMORY_INFO meminfo);
//定義內存信息結構
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_INFO
{
public uint dwLength;
public uint dwMemoryLoad;
public uint dwTotalPhys;
public uint dwAvailPhys;
public uint dwTotalPageFile;
public uint dwAvailPageFile;
public uint dwTotalVirtual;
public uint dwAvailVirtual;
}
//獲取系統信息
public void GetMemoryInfo()
{
MEMORY_INFO MemInfo;
MemInfo = new MEMORY_INFO();
GlobalMemoryStatus(ref MemInfo);
Console.WriteLine(MemInfo.dwMemoryLoad.ToString() + "%內存正在使用");//
Console.WriteLine("物理內存:" +(double.Parse(MemInfo.dwTotalPhys.ToString())/1048576).ToString()+ "MB。");
Console.WriteLine("可用物理內存" +(double.Parse(MemInfo.dwAvailPhys.ToString())/1048576).ToString() + "MB。");
}
//獲取當前狀態的可用內存大小,並輸出到控制檯
public void GetAvailMemory(ref string befLoop)
{
MEMORY_INFO MemInfo;
MemInfo = new MEMORY_INFO();
GlobalMemoryStatus(ref MemInfo);
befLoop=MemInfo.dwAvailPhys.ToString();
Console.WriteLine("當前可用內存大小爲:"+double.Parse(befLoop)/1048576+"MB。");
}
//控制檯輸出循環前後消耗的內存大小
public void MemoryCostPrint(string beforeLoop,string afterLoop)
{
Double bef = Double.Parse(beforeLoop);
Double aft = Double.Parse(afterLoop);
Console.WriteLine("共消耗:"+((bef-aft) / 1048576).ToString()+"MB內存。");
}
}
(1)時間複雜度分析
主要代碼片段:
Console.WriteLine("********************************String類型******************************");
Console.WriteLine("----循環前:");
MemInfo.GetAvailMemory(ref befLoop); //獲取循環前可用內存的大小
string str = "a";
Console.WriteLine("變量長度:" + str.Length.ToString());
beginTime = DateTime.Now; //獲取循環前的時間
for (int i = 0; i < count; i++) //count=10000
{
str += "a";
}
TimeSpan time = (DateTime.Now - beginTime); //獲得循環前後的時間差
Console.WriteLine("----循環後:");
MemInfo.GetAvailMemory(ref aftLoop); //獲取循環後可用內存的大小
MemInfo.MemoryCostPrint(befLoop, aftLoop);
Console.WriteLine("變量長度:" + str.Length.ToString());
Console.WriteLine("共消耗時間:" + (time.TotalMilliseconds / 1000).ToString() + "秒。");
運行結果:
(2)空間複雜度分析
主要代碼:
Console.WriteLine("********************************StringBuilder類型******************************");
System.DateTime beginTime_strBui = new DateTime();
MemInfo.GetAvailMemory(ref strBui_befLoop);
StringBuilder strBui = new StringBuilder("a");
Console.WriteLine("循環前變量長度:" + strBui.Length.ToString());
beginTime_strBui = DateTime.Now;
for (int i = 0; i < count; i++)
{
strBui.Append("a");
}
TimeSpan strBui_time = (DateTime.Now - beginTime_strBui);
MemInfo.GetAvailMemory(ref strBui_aftLoop);
MemInfo.MemoryCostPrint(strBui_befLoop, strBui_aftLoop);
Console.WriteLine("循環後變量長度:"+strBui.Length.ToString());
Console.WriteLine("共消耗時間:" + strBui_time.TotalMilliseconds.ToString() + "毫秒.");
運行結果:
Str【String類型】和StrBui【StringBuilder類型】兩個變量在循環前後的值都一樣,循環前爲"a",循環後爲一萬零一個"a"。但實現這個一萬次修改所消耗的系統資源相差懸殊:
內存消耗:str約爲4.73MB,strBui約爲0.45MB,相差10倍以上。
時間消耗:str約爲24.09秒,strBui約爲15.63毫秒,相差1541倍。