6 | 字符串
6 | 字符串
C#的string是不可变的,在通常情况下,一旦生成,就无法被改变(使用Unsafe除外),这样会导致两个问题:
- 每一次拼接或修改字符串都会生成一个新字符串,一般旧的字符串就会作为垃圾存在。
- 内存中会出现多份内容一样的字符串资源,如两次用相同方法拼接的字符串。这样会造成内存浪费。这里有一个例外,就是“abc”+“def”的形式,会被编译器直接编译成“abcdef”,当然基本上也没有人会这么写。
针对第二个问题,在C#中有一个stringpool可以解决,就是一个内置的字符串池,是一个哈希表的数据结构,一般代码中用直接写的字符串,如“abcdef”,会存在于stringpool中,而拼接的字符串,可以使用string.Intern(str); 把字符串放在stringpool中并指向在stringpool中的实例。
看看下面几个测试用例(Assert.AreSame的意思是两个对象引用的地址相同):
a: Assert.AreSame("12", "12".ToString());
b: Assert.AreSame("12", 12.ToString());
c: Assert.AreSame("12", string.Intern(12.ToString()));
d: Assert.AreSame("12", "1" + "2");
e: Assert.AreSame("12", "1".ToString() + "2");
- a:“12”是在stringpool中的,字符串的 ToString() 方法并不能生成新的字符串,所以这个会通过。
- b:12.ToString() ,会生成一个新的 “12” 实例,所以这个会失败。
- c:string.Intern 会返回指向stringpool中的地址,新生成的实例会被抛弃,所以这个会通过。
- d:这个看似是字符串拼接,但是一般编译器会将其优化成 “12” ,所以这个会通过。
- e:虽然 “1”.ToString() 返回的是 “1” ,在stringpool的实例中看似和d相同,可是这个不会被Mono或IL2CPP编译器优化掉,拼接后生成了一个新字符串,所以这个会失败。
6.1 自定义string内存池
内置的内存池有两个缺点:
- 无法清空,比如我在这次战斗中经常会生成的字符串,在下一场战斗中不会经常生成,如人名等,使用内置的stringpool会造成内存泄漏。
- 每次调用string.Intern会将生成的字符串抛弃,如果频繁使用会产生很多的垃圾。
首先是要分清哪些字符串是需要在某一时刻清空的,哪些时候是可以常驻内存的,如:
- 整数型:比如倒计时”12s“之类的。可以使用单独的一个池常驻内存,防止每次都生成新的字符串,如:
public struct Key
{
public string format;
public int number;
}
public class KeyComparer : IEqualityComparer<Key>
{
...
}
Dictionary<Key, string> intToStringPool;
这种第一次生成字符串时可以调用string.Intern再放入字典中,保证全局唯一。
- 常用名称拼接,一般两到三个字符串拼在一起:如 “gun_” + “ak47” 这种形式,可以常驻内存,例子和上面的差不多。
- xx击杀了yy,这种形式:只在当前战斗有效,如果想缓存,需要单独一个pool,战斗结束后需要清空。
浙公网安备 33010602011771号