string和stringbuilder的解剖

 

StringStringBuilder的深入解析

 

前言:本文出发点是我们开发的过程中是否真正的理解stringbuilder的使用,string字符串操作的是如何实现(哈希表),stringbuilder是否设置默认值容量,什么时候才用stringbuilder。

 

一概念Stringstringbulider的理解

 

string是我们用的最多的类型之一,是一个特殊的引用类型,直接派生于Object,因此它的值储存在托管堆上。构造一个新字符串的时候,不需要用new。它是”不可变的“。初始化字符串对象后,该字符串对象的长度、内容都是确定不变的了。可以思考这个时候,我们需要更改或者添加字符串,会做一个怎样的动作呢?

 

   StringBulider因为string的”不可变“,导致多次修改字符串的时候会损耗性能,.net为了解决这个问题,提供了动态创建string的方法,以克服string不可变带来的性能损耗。StringBuilder和String比起来,功能较少,只有基本的属性和增删改的方法。但是你知道stringbuilder也有一个固定的容量值吗??注意:StringBulider容量  (默认是16虽然   StringBuilder   对象是动态对象,允许扩充它所封装的字符串中字符的数量,但是您可以为它可容纳的最大字符数指定一个值。此值称为该对象的容量,不应将它与当前   StringBuilder   对象容纳的字符串长度混淆在一起。例如,可以创建   StringBuilder   类的带有字符串“Hello”(长度为   5)的一个新实例,同时可以指定该对象的最大容量为   25。当修改   StringBuilder   时,在达到容量之前,它不会为其自己重新分配空间。当达到容量时,将自动分配新的空间且容量翻倍。可以使用重载的构造函数之一来指定StringBuilder类的容量。以下代码示例指定可以将   MyStringBuilder对象扩充到最大25个空白。 StringBuilder MyStringBuilder  =  new   StringBuilder("Hello   World!",   25);   另外,可以使用读/写   Capacity   属性来设置对象的最大长度。以下代码示例使用   Capacity   属性来定义对象的最大长度。  MyStringBuilder.Capacity   =   25;   EnsureCapacity   方法可用来检查当前   StringBuilder   的容量。如果容量大于传递的值,则不进行任何更改;但是,如果容量小于传递的值,则会更改当前的容量以使其与传递的值匹配。 也可以查看或设置   Length   属性。如果将   Length   属性设置为大于   Capacity   属性的值,则自动将   Capacity   属性更改为与   Length   属性相同的值。如果将   Length   属性设置为小于当前   StringBuilder   对象内的字符串长度的值,则会缩短该字符串。

 

、为什么说变动影响性能。(stringStringBuilder

 String:string s = "I am ";s += "Sky";怎么分配内存的呢?

 

  备注:如果每次都这样重新分配真实疯了,.net肯定没有那么傻了,最起码要避免下如果两个string的字符串一模一样,我是不是不需要分配新的堆,只需要制定同样的引用就好了呢?下面就出现了一个名词:字符串留用,CLR初始化的时候会创建哈希表,每构建一个新字符串都会与哈希表匹配,查找是否有相同的字符串,如果匹配,就会返回这个已存在的旧对象,由新变量进行引用。否则,就会创建一个字符串副本添加到哈希表里,Key就是字符串,Value就是string对象在堆上的地址。

 

是不是所有的都是这样呢,有什么特殊情况吗?

 

总结New出来的对象是不会记录在哈希表。

tringBuilder 对象维护一个缓冲区,以便容纳新数据的串联。如果有足够的空间,新数据将被追加到缓冲区的末尾;否则,将分配一个新的、更大的缓冲区,原始缓冲区中的数据被复制到新的缓冲区,然后将新数据追加到新的缓冲区。

 

 

内部实现原理

 

总结:StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。 每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量。事实是,StringBuilder比string快的原因是string拼接时产生了中间对象,最终是垃圾。如: string str = "a";str += "b";str += "c";那么,最终结果是"abc",但第二行产生了"ab"只是一个中间对象,是个垃圾。用StringBuilder会避免这种中间对象的产生。那如果我这么写呢? string str ="a"+"b"+ "c";这会比StringBuilder慢吗?不会。

中间对象的产生才是影响性能的主要原因。

 

三、测试案例:

 

  private void button1_Click(object sender, EventArgs e)

        {

 

            int number =int.Parse( textBox1.Text.ToString());

 

            GetStringTime(number);

            GetStringBulider(number);

 

            GetStringTime1(number);

            GetStringBulider1(number);

        }

 

        /// <summary>

        ///  测试string 性能时间

        /// </summary>

        private void GetStringTime(int number)

        {

            Stopwatch watch = new Stopwatch();

            List< String> li = new List< string>();

            watch.Start();

            string str = "select * from testa inner join 快速排序耗费时间 快速排序耗费时间 快速排序耗费时间";

            for (int i = 0; i < number; i++)

            {

                li.Add(str);    

            }

            watch.Stop();

            label1.Text = watch.ElapsedMilliseconds.ToString();

 

        }

 

 

        private void GetStringBulider(int number)

        {

            Stopwatch watch = new Stopwatch();

            List<String> li = new List<string>();

            watch.Start();

            StringBuilder strb = new StringBuilder();

            strb.Append("select * from testa inner join dsadfasfsadfa快速排序耗费时间快速排序耗费时间");

            for (int i = 0; i < number; i++)

            {

                li.Add(strb.ToString());   

            }

            watch.Stop();

            label2.Text = watch.ElapsedMilliseconds.ToString();

       

        }

 

 

        /// <summary>

        ///  测试string 性能时间变化

        /// </summary>

        private void GetStringTime1(int number)

        {

            Stopwatch watch = new Stopwatch();

            List<String> li = new List<string>();

            watch.Start();

            string str = "select * from testa inner join 快速排序耗费时间 快速排序耗费时间 快速排序耗费时间";

            for (int i = 0; i < number; i++)

            {

                str = str+"select * from testa inner join 快速排序耗费时间 快速排序耗费时间 快速排序耗费时间";

          

            }

            watch.Stop();

            label1.Text =label1.Text+"不变,变化"+  watch.ElapsedMilliseconds.ToString();

 

        }

 

 

        /// <summary>

        ///  测试stringBulider  变化的性能

        /// </summary>

        /// <param name="number"></param>

        private void GetStringBulider1(int number)

        {

            Stopwatch watch = new Stopwatch();

            List<String> li = new List<string>();

            watch.Start();

            StringBuilder strb = new StringBuilder();

            strb.Append("select * from testa inner join dsadfasfsadfa快速排序耗费时间快速排序耗费时间");

            for (int i = 0; i < number; i++)

            {

                strb.Append("select * from testa inner join dsadfasfsadfa快速排序耗费时间快速排序耗费时间");

   

            }

            watch.Stop();

            label2.Text =label2.Text+"不变,变化"+ watch.ElapsedMilliseconds.ToString();

 

        }

 

效果图如下:

 

 

 

 

备注:每图第一行表示string,第二行表示stringbulider,变化表示str++的意思或append。

四:总结:String stringbulider的整体汇总。

1. Sting是恒定的,string部里的人是可变化的。

2. 对于简单的字符串连接操作,在性能上stringbuilder不一定总是优于string。因为stringbulider对象的创建也消耗大量的性能,在字符串连接比较少的情况下,过度滥用stringbuilder会导致性能的浪费而非节约,只有大量无法预知次数的字符串操作才考虑stringbuilder的使用。从最后分析可以看出如果是100行以内根本看不出太大差别。

3. Stringbulider的使用,最好制定合适的容量值,否则优于默认值容量不足而频繁的进行内存分配操作,是不妥的实现方法。

 

 

 可以深思,第一我们对适合的容量值处理了吗? 第二,我们是不是一再提要使用stringbuilder说性能好,但是在100行内的字符操作有分别吗。

简单的字符串连接操作可以适度思考下 string.Concat 和 string.join 的使用。(string.concat的装箱操作)。

 

 

参考文章:

http://www.cnblogs.com/juqiang/archive/2005/04/19/140538.html

http://www.cnblogs.com/huangxincheng/p/4042105.html

http://www.cnblogs.com/heartstill/archive/2011/11/11/2245411.html

http://www.cnblogs.com/kid-li/archive/2006/10/18/532174.html

http://www.cnblogs.com/gfwei/archive/2007/03/14/674499.html

http://www.cnblogs.com/skychen1218/p/3593678.html

书籍:《你必须知道的.net》什么是string345页)

posted @ 2015-09-21 15:10  坤灵码农  阅读(6174)  评论(0编辑  收藏  举报