字符串优化

在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来构造某个字符串,伪代码如下:

1
2
3
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之后存起来,下次想用的时候直接拿出来用。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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答的,没用过。说是性能很好,但是有一堆限制。

posted @   mc宇少  阅读(87)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示