深度分析String类型与StringBuilder类型

来源:互联网 发布:linux远程桌面windows 编辑:程序博客网 时间:2024/05/18 06:24

 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倍。