字符串留用

字符串是不可变的,一经声明便无法修改,每次修改都是创建一个新的string对象,这意味着如果执行大量的字符串操作将会在堆上创建大量的string对象,造成更加频繁的垃圾回收,损害程序的性能。

避免这个问题除了使用StringBuilder类操作字符串外还可以使用"字符串留用"。

字符串留用:

因为字符串的不可变性,所以如果对相同字符串在内存中只保留一个字符串实例,后续声明的相同对象只需指向该内存,将大大提高字符串的利用率。字符串留用便是在CLR内部创建一个哈希表,声明字符串的时候查询哈希表中是否有该引用,有则返回该引用,否则将字符串作为Key,引用作为Value存到哈希表中,从而提高字符串的利用率。

声明字符串,每次都是新的引用对象

string str1 = "A";
string str2 = "A";
//在CLR的4.0之前的版本结果为false
Console.WriteLine(object.ReferenceEquals(str1, str2));

上述代码运行结果输出false,即使相同的值,引用也不同,即创建了新的string对象。

使用字符串留用

string str1 = string.Intern("A"); //字符串留用
string str2 = string.Intern("A"); //字符串留用
//结果true
Console.WriteLine(object.ReferenceEquals(str1, str2));

字符串留用API:string.Intern(string str)
获取一个string,获得该string的哈希码,并在内部哈希表中检查是否有完全相同的匹配项,如果有返回已经存在的字符串引用,否则创建新的字符串,并添加到哈希表中且返回引用。

*字符串的内存可被垃圾回收,垃圾回收器不能释放哈希表内的引用(除非卸载AppDomain或进程终止)

除了Intern方法外,还有一个string.IsInterned(string str)

// 显示声明,自动留用
String str1 = "abcd";
String str1Interned = String.IsInterned(str1);
//false str1Interned = "abcd"
Console.WriteLine(str1Interned == null);

// 非显示声明,不会自动留用
String str2 = new StringBuilder().Append("x").Append("y").Append("z").ToString();
String str2Interned = String.IsInterned(str2);
//true
Console.WriteLine(str2Interned == null);

通过代码段可知,IsInterned方法与Intern相似,都是在池中寻找是否有完全匹配项,如果有则返回引用,不同点在于没有匹配项时Intern创建并插入到哈希表中且返回该引用,而IsInterned在找不到匹配字符串时直接返回null。

总结: 字符串留用可以减少频繁操作字符串带来的性能损耗,但同时也应该注意使用字符串留用哈希表内的引用不会被垃圾回收,即使该字符串永远不再被使用,也不会得到释放,直到进程终止。