[4/11]C#性能优化-String 操作-每个细节都有示例代码

[4/11]C#性能优化-String 操作-每个细节都有示例代码_第1张图片

前言

在C#开发中,性能优化是提升系统响应速度和资源利用率的关键环节。
当然,同样是所有程序的关键环节。
通过遵循下述建议,可以有效地减少不必要的对象创建,从而减轻GC的负担,提高应用程序的整体性能。记住,优化应该是有针对性的,只有在确定了性能瓶颈之后,才应该采取相应的措施。

4. String 操作

4.1.使用 StringBuilder 做字符串连接

String 是不变类,使用 + 操作连接字符串将会导致创建一个新的字符串。如果字符串连接次数不是固定的,例如在一个循环中,则应该使用 StringBuilder 类来做字符串连接工作。因为 StringBuilder 内部有一个 StringBuffer ,连接操作不会每次分配新的字符串空间。只有当连接后的字符串超出 Buffer 大小时,才会申请新的 Buffer 空间。典型代码如下:

StringBuilder sb = new StringBuilder( 256 );
for ( int i = 0 ; i < Results.Count; i ++ )
{
    sb.Append (Results[i]);
}

如果连接次数是固定的并且只有几次,此时应该直接用 + 号连接,保持程序简洁易读。实际上,编译器已经做了优化,会依据加号次数调用不同参数个数的 String.Concat 方法。例如:

string str = str1 + str2 + str3 + str4;

会被编译为 String.Concat(str1, str2, str3, str4)。该方法内部会计算总的 string 长度,仅分配一次,并不会如通常想象的那样分配三次。作为一个经验值,当字符串连接操作达到 10 次以上时,则应该使用 StringBuilder。

这里有一个细节应注意:StringBuilder 内部 Buffer 的缺省值为 16 ,这个值实在太小。按 StringBuilder 的使用场景,Buffer 肯定得重新分配。经验值一般用 256 作为 Buffer 的初值。当然,如果能计算出最终生成字符串长度的话,则应该按这个值来设定 Buffer 的初值。使用 new StringBuilder(256) 就将 Buffer 的初始长度设为了256。

4.2.避免不必要的调用 ToUpper 或 ToLower 方法

string是不变类,调用ToUpperToLower方法都会导致创建一个新的字符串。如果被频繁调用,将导致频繁创建字符串对象。这违背了前面讲到的“避免频繁创建对象”这一基本原则。

例如,bool.Parse方法本身已经是忽略大小写的,调用时不要调用ToLower方法。

另一个非常普遍的场景是字符串比较。高效的做法是使用 Compare 方法,这个方法可以做大小写忽略的比较,并且不会创建新字符串。

还有一种情况是使用 HashTable 的时候,有时候无法保证传递 key 的大小写是否符合预期,往往会把 key 强制转换到大写或小写方法。实际上 HashTable 有不同的构造形式,完全支持采用忽略大小写的 key: new HashTable(StringComparer.OrdinalIgnoreCase)

4.3.最快的空串比较方法

将String对象的Length属性与0比较是最快的方法:

if (str.Length == 0)

其次是与String.Empty常量或空串比较:

if (str == String.Empty)if (str == "")

注:C#在编译时会将程序集中声明的所有字符串常量放到保留池中(intern pool),相同常量不会重复分配。

4.4 使用StringBuilder做字符串连接

使用StringBuilder做字符串连接
在C#中,当你需要进行大量的字符串连接操作时,使用StringBuilder类通常比直接使用字符串连接(如使用+运算符)更加高效。这是因为字符串在C#中是不可变的,每次修改字符串实际上都是创建了一个新的字符串对象,这会导致性能下降和额外的内存消耗,特别是当连接操作在一个循环内执行时。

StringBuilder的优势

  • 可变性StringBuilder允许你在同一个实例上进行多次修改而不创建新的对象。
  • 性能:对于大量字符串拼接操作,使用StringBuilder能显著提高性能,因为它减少了不必要的对象创建和垃圾回收。
  • 灵活性:提供了丰富的方法来插入、追加、移除或替换子字符串。

使用StringBuilder的例子

下面是一个简单的示例,演示了如何使用StringBuilder来进行字符串连接:

using System;
using System.Text;

class Program
{
    static void Main()
    {
        StringBuilder sb = new StringBuilder();

        // Append some strings
        sb.Append("Hello, ");
        sb.Append("world");
        sb.Append("!");

        // Insert a string at the beginning
        sb.Insert(0, "Message: ");

        // Replace part of the string
        sb.Replace("world", "C# user");

        Console.WriteLine(sb.ToString());
    }
}

这段代码首先创建了一个StringBuilder实例,然后通过调用其Append方法添加了一些字符串。接着,它在字符串的开头插入了一段文本,并且替换了其中的一部分内容。最后,通过调用ToString()方法将StringBuilder的内容转换为一个普通的字符串并打印出来。

何时使用StringBuilder

  • 当你需要对同一个字符串进行多次修改时。
  • 尤其是在循环内部进行字符串连接时,使用StringBuilder可以避免由于频繁创建临时字符串对象而导致的性能问题。

总之,虽然对于少量的字符串连接操作来说,直接使用+运算符既简单又足够高效,但对于涉及到大量字符串操作的情况,使用StringBuilder能够带来明显的性能提升。

4.4. 拼装字符串

拼装好之后再删除是很低效的写法。有些方法其循环长度在大部分情况下为1,这种写法的低效就更为明显了:

public static string ToString(MetadataKey entityKey)
{
    string str = "" ;
    object [] vals = entityKey.values;
    for ( int i = 0 ; i < vals.Length; i ++ )
    {
        str += " , " + vals[i].ToString();
    }
    return str == "" ? "" : str.Remove( 0, 1 );
}

推荐下面的写法:

if (str.Length == 0 )
    str = vals[i].ToString();
else
    str += " , " + vals[i].ToString();

其实这种写法非常自然,而且效率很高,完全不需要用个Remove方法绕来绕去。

推荐的改进方法

推荐的写法通过条件判断来避免不必要的字符串拼接,但这种方法仍然不是最优解,特别是在处理多个字符串拼接时。更高效的解决方案是使用StringBuilder类,它专门用于解决频繁修改字符串的情况。

使用StringBuilder的示例

这里提供一个基于StringBuilder的优化版本:

public static string ToString(MetadataKey entityKey)
{
    StringBuilder strBuilder = new StringBuilder();
    object[] vals = entityKey.values;
    for (int i = 0; i < vals.Length; i++)
    {
        if (i > 0) // 在非首次添加前加上", "
            strBuilder.Append(", ");
        strBuilder.Append(vals[i].ToString());
    }
    return strBuilder.ToString();
}

StringBuilder的优势

  • 高效性StringBuilder减少了因字符串拼接造成的临时对象创建,降低了内存使用和垃圾回收的压力。
  • 灵活性:可以方便地追加、插入、移除字符或子串,以及执行其他字符串操作。

这种方法不仅提高了效率,还使得代码更加清晰易读。因此,在需要频繁修改字符串的场景下,推荐使用StringBuilder而非直接使用+运算符拼接字符串。

你可能感兴趣的:(c#,c#,性能优化,开发语言)