在C#开发中,性能优化是提升系统响应速度和资源利用率的关键环节。
当然,同样是所有程序的关键环节。
通过遵循下述建议,可以有效地减少不必要的对象创建,从而减轻GC的负担,提高应用程序的整体性能。记住,优化应该是有针对性的,只有在确定了性能瓶颈之后,才应该采取相应的措施。
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。
string是不变类
,调用ToUpper
或ToLower
方法都会导致创建一个新的字符串
。如果被频繁调用,将导致频繁创建字符串对象。这违背了前面讲到的“避免频繁创建对象”这一基本原则。
例如,bool.Parse方法本身已经是忽略大小写的,调用时不要调用ToLower方法。
另一个非常普遍的场景是字符串比较。高效的做法是使用 Compare 方法,这个方法可以做大小写忽略的比较,并且不会创建新字符串。
还有一种情况是使用 HashTable 的时候,有时候无法保证传递 key 的大小写是否符合预期,往往会把 key 强制转换到大写或小写方法。实际上 HashTable 有不同的构造形式,完全支持采用忽略大小写的 key: new HashTable(StringComparer.OrdinalIgnoreCase)
。
将String对象的Length属性与0比较是最快的方法:
if (str.Length == 0)
其次是与String.Empty常量或空串比较:
if (str == String.Empty)或if (str == "")
注:C#在编译时会将程序集中声明的所有字符串常量放到保留池中(intern pool),相同常量不会重复分配。
使用StringBuilder做字符串连接
在C#中,当你需要进行大量的字符串连接操作时,使用StringBuilder
类通常比直接使用字符串连接(如使用+
运算符)更加高效。这是因为字符串在C#中是不可变的,每次修改字符串实际上都是创建了一个新的字符串对象,这会导致性能下降和额外的内存消耗,特别是当连接操作在一个循环内执行时。
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
能够带来明显的性能提升。
拼装好之后再删除是很低效的写法。有些方法其循环长度在大部分情况下为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
而非直接使用+
运算符拼接字符串。