• C# String 字符拼接测试(“+”、string.Format、StringBuilder 比较)


    对于字符串的拼接自己一直有疑问,在何时该用什么方法来拼接?哪种方法更好、更适合。

    几种方法


    1、“+” 拼接字符串

    现在在 C# 中,字符串进行拼接,可以直接用 “+” 而且可以直接用于数字类型的而不必转换(整形、浮点等都可以)

    string a = "1";
    
    a = a + "1";
    
    string b = "2" + 1.2345;

    对于使用多个 “+” 的,编译器会优化为:

     string a = "a" + 1 + "b" + 2 + "c" + 3 + "d" + 4;
     string a = string.Concat(new string[]{});

    通过分析string.Concat(params string[] values)的实现可以知道:先计算目标字符串的长度,然后申请相应的空间,最后逐一复制,时间复杂度为o(n),常数为1。

    固定数量的字符串连接效率最高的是+。

    但是字符串的连+不要拆成多条语句,比如:

                string a = "a";
                a += 1;
                a += "b";
                a += 2;
                a += "c";
                a += 3;

    这样的代码,不会被优化为string.Concat,就变成了性能杀手,因为第i个字符串需要复制n-i次,时间复杂度就成了o(n^2)。

    那么用 “+” 拼接字符串也是要正确运用。

    2、string.Format 拼接字符串

    该形式可以同时拼接多个字符串

    string.Format("{0}{1}{2}{3}","a","b","c","d");

    它的底层是 StringBuilder,在此基础进行了多层的封装,说是效率和 StringBuilder 差不多,这个不清楚,可以看下面的实验;

    3、StringBuilder 拼接字符串

    StringBuilder str = new StringBuilder();
    str.Append("a");

    StringBuilder 只分配一次内存,如果第二次连接内存不足,则修改内存大小;它每次默认分配16字节,如果内存不足,则扩展到32字节,如果仍然不足,继续成倍扩展。

    如果频繁的扩展内存,效率大打折扣,因为分配内存,时间开销相对比较大。如果事先能准确估计程序执行过程中所需要的内存,从而一次分配足内存,效率大大提高。

    如果字符串的数量不固定,就用StringBuilder,一般情况下它使用2n的空间来保证o(n)的整体时间复杂度,常数项接近于2。

    因为这个算法的实用与高效,.net类库里面有很多动态集合都采用这种牺牲空间换取时间的方式,一般来说效果还是不错的。

    4、List<string> 拼接字符串

                List<string> str =new List<string>();
                str.Add("1");
                str.Add("a");
                str.Add("2");
                str.Add("b");
                string.Join("",str);

    它可以转换为string[]后使用string.Concat或string.Join,很多时候效率比StringBuiler更高效。List与StringBuilder采用的是同样的动态集合算法,时间复杂度也是O(n),与StringBuilder不同的是:List的n是字符串的数量,复制的是字符串的引用;StringBuilder的n是字符串的长度,复制的数据。不同的特性决定的它们各自的适应环境,当子串比较大时建议使用List<string>,因为复制引用比复制数据划算。而当子串比较小,比如平均长度小于8,特别是一个一个的字符,建议使用StringBuilder。

    MSDN中关于StringBuilder的性能注意事项:

    Concat 和 AppendFormat 方法都将新数据串连到一个现有的 String 或 StringBuilder 对象。String 对象串联操作总是用现有字符串和新数据创建新的对象。StringBuilder 对象维护一个缓冲区,以便容纳新数据的串联。如果有足够的空间,新数据将被追加到缓冲区的末尾;否则,将分配一个新的、更大的缓冲区,原始缓冲区中的数据被复制到新的缓冲区,然后将新数据追加到新的缓冲区。

    String 或 StringBuilder 对象的串联操作的性能取决于内存分配的发生频率。String 串联操作每次都分配内存,而 StringBuilder 串联操作仅当 StringBuilder 对象缓冲区太小而无法容纳新数据时才分配内存。因此,如果串联固定数量的 String 对象,则 String 类更适合串联操作。这种情况下,编译器甚至会将各个串联操作组合到一个操作中。如果串联任意数量的字符串,则 StringBuilder 对象更适合串联操作;例如,某个循环对用户输入的任意数量的字符串进行串联。

    测试

    测试1:

                Stopwatch watch = new Stopwatch();
                watch.Start();
    
                string a = "1";
    
                for (int i = 0; i < 1000000; i++)
                {
                    a = a + "1" ;
                }
    
                watch.Stop();
    
                Console.WriteLine("+ 用时:" + watch.ElapsedMilliseconds + " ms");
    
                string b = "1";
                watch.Restart();
    
                for (int i = 0; i < 1000000; i++)
                {
                    b = string.Format("{0}{1}", b, "1");
                }
    
                watch.Stop();
    
                Console.WriteLine("Format 用时:" + watch.ElapsedMilliseconds + " ms");
    
                StringBuilder str = new StringBuilder();
                char charT = '1';
                watch.Restart();
    
                for (int i = 0; i < 1000000; i++)
                {
                    str.Append("1");
                }
    
                watch.Stop();
    
                Console.WriteLine("StringBuilder 用时:" + watch.ElapsedMilliseconds + " ms");    

    对上面的几种分别进行100000 次循环,并计时,结果是:

     

    进行1000000 次循环拼接字符串时,时间太长,没有截图

     从上面两个来看,String.Format 效率并不高,远远达不到 StringBuildr 的效率,也差于 “+” 拼接字符串;

    对代码进行改进:

                Stopwatch watch = new Stopwatch();
                watch.Start();
    
                string a = "1";
    
                for (int i = 0; i < 100000; i++)
                {
                    a = a + "1" + "1" + "1" + "1" + "1" + "1" + "1" + "1" + "1" + "1";
                }
    
                watch.Stop();
    
                Console.WriteLine("+ 用时:" + watch.ElapsedMilliseconds + " ms");
    
                string b = "1";
                watch.Restart();
    
                for (int i = 0; i < 100000; i++)
                {
                    b = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}", b, "1", "1", "1", "1", "1", "1", "1", "1", "1", "1");
                }
    
                watch.Stop();
    
                Console.WriteLine("Format 用时:" + watch.ElapsedMilliseconds + " ms");
    
                StringBuilder str = new StringBuilder();
                char charT = '1';
                watch.Restart();
    
                for (int i = 0; i < 100000; i++)
                {
                    str.Append(charT, 10);
                }
    
                watch.Stop();
    
                Console.WriteLine("StringBuilder 用时:" + watch.ElapsedMilliseconds + " ms");    

    进行 100000 次循环,每次进行10个字符,结果如下:

    从这个结果看,String.Format 效率还是较差。

    文章中部分引用自:

    https://www.cnblogs.com/popzhou/p/3676691.html

  • 相关阅读:
    php查看网页源代码的方法
    php阻止网页被用户频繁刷新
    php实现只保留mysql中最新1000条记录
    php限定时间内同一ip只能访问一次
    emmet插件快捷键:
    抓包工具--Fiddler
    HTTP版本进化过程
    ECMAScript6的Promise对象
    H5、CSS3属性的支持性以及flex
    关于未来前端的规划
  • 原文地址:https://www.cnblogs.com/zhurong/p/9446217.html
Copyright © 2020-2023  润新知