字符串优化
在C#中,string是引用类型,每次动态创建一个string,c#都会在堆内存中分配一个内存用于存放字符串(包括字符串拼接、字符串分割等)。
什么地方会导致字符串GC高:
1.字符串拼接
用StringBuilder的Append代替+;
2.数字类型转为字符串产生的GC
将数字转成char[](这个char[]存下来复用),然后写入到StringBuilder中。
3.值类型在format时的拆装箱
扩展stringbuilder使其支持泛型。
4.Split导致的GC
1.如果只需要字符串的一部分,那split操作可以使用indexof找到位置,再substring,可以省点空间。
2.复杂的分隔符可以使用正则Regex.Split。
优化方案有以下几种:
1.自建缓存机制
可以用一些标志性的Key值来一一对应字符串,比如游戏项目中常用ID来构造某个字符串,伪代码如下:
ResData data = GetDataById(Id); string strName = "xxxx" + data.Name;
可以用字典将strName缓存起来,用id当key,尝试复用。
2.用C#的一些“不安全”的native方法
也就是直接使用指针来改变string中字符串的值,这样就可以重复利用string,而不需要重新分配内存。
实现:将不用的字符串用长度当key缓存起来,要申请新的字符串的时候尝试从缓存里拿并通过指针将字符数组(字符串本质上还是字符数组)改成需要的值。
3.使用StringBuilder
stringbuilder的原理是内部维护一个char数组,所有字符串的操作都是在操作这个char数组,不像string是直接建一个新的出来。
1.字符串拼接
Append():向字符数组里面加字符,默认长度是16,如果容量不够就自动扩容。java实现扩容的方式就是像List一样,复制一个二倍的出来,而C#是使用链表。
发生扩容就建一个新的StringBuilder存储该stringbuilder存不下的内容,并使用链表的方式连接。


2.ToString
倒序遍历链表拿出数组来构建string对象。
3.stringbuilder复用
Clear的实现是将length改成0,改length的操作是newLength > oldLength:扩容,并填充null。反之就是截断链表,但不会清掉其内存。
所以stringbuilder用完可以把它clear之后存起来,下次想用的时候直接拿出来用。
例子:
StringBuilder sb = new StringBuilder();
// 使用StringBuilder对象
foreach (var whatever in whateverlist)
{
sb.Append($" {whatever}");
}
// 清除StringBuilder对象的内容
sb.Clear(); // 或者 sb.Length = 0;
// 再次使用StringBuilder对象
foreach (var whatever2 in whateverlist2)
{
sb.Append($" {whatever2}");
}
4.AppendFormat
stringbuilder的AppendFormat参数是object,用来拼接值类型会有拆装箱的问题。
优化:扩展一个支持泛型参数的格式化追加函数AppendFormat<TP1..TPn>(),以避免垃圾回收开销。
实现:https://zhuanlan.zhihu.com/p/668253748
4.zstring
总的来说,ZString的实现原理是通过在非托管堆上申请内存并使用Span进行字符串拼接,从而避免了在托管堆上进行内存分配和回收,提高了性能。
AI答的,没用过。说是性能很好,但是有一堆限制。

浙公网安备 33010602011771号