C#中字符串的内存分配与驻留池
刚開始学习C#的时候。就听说CLR对于String类有一种特别的内存管理机制:有时候。明明声明了两个String类的对象。可是他们偏偏却指向同一个实例。
例如以下:
String s1 = "Hello";
String s2 = "Hello";
//s2和s1的实际值都是Hello
bool same = (object) s1 == (object) s2;
//这里比較s1、s2是否引用了同一个对象实例
//所以不能写作bool same = s1 == s2;
//由于String类重载了==操作符来比較String对象包括的实际值
这里的same会被赋值为true。也就是说s1真的和s2引用了同一个String对象。当然。应该注意到的是s1和s2都被统一赋值为同一个字符串Hello。这才是出现上述情况的原因。
如今我们初步得出结论,当有多个字符串变量包括了相同的字符串实际值时,CLR可能不会为它们反复地分配内存,而是让它们统统指向同一个字符串对象实例。(这里我说了可能,是由于某些情况下,确实也会发生同一个字符串实际值在内存中有多份副本同一时候存在。请继续往下看。)
我们知道。String类有非常多特别的地方,当中之中的一个就是它是不可变性(immutable)。
这说明在我们每次对一个String对象进行操作时(比方说使用Trim,Replace等方法),并非真的对这个String对象的实例进行改动。而是返回一个新的String对象实例作为操作运行的结果。String对象的实例一经生成。到死都不会被改变了!
基于String类这种特性,CLR让表示同样的字符串实际值的变量指向同一个String事例,就是全然合理的了。由于利用不论什么一个对String实例的引用所进行的改动操作都不会切实地影响到该实例的状态。也就不会影响到其它全部指向该实例的引用所表示的字符串实际值。CLR如此管理String类的内存分配,能够优化内存的使用情况。避免内存中包括冗余的数据。
为了实现这个机制,CLR默默地维护了一个叫做驻留池(Intern Pool)的表。这个表记录了全部在代码中使用字面量声明的字符串实例的引用。这说明使用字面量声明的字符串会进入驻留池。而其它方式声明的字符串并不会进入,也就不会自己主动享受到CLR防止字符串冗余的机制的优点了。这就是我上文提到的某些情况下,确实也会发生同一个字符串实际值在内存中有多份副本同一时候存在的样例。请看这个样例:
StringBuilder sb = new StringBuilder();
sb.Append("He").Append("llo");string s1 = "Hello";
string s2 = sb.ToString();
bool same = (object) s1 == (object) s2;
这时same就不是true了,由于尽管s1。s2表示的是同样的字符串,可是由于s2不是通过字面量声明的,CLR在为sb.ToString()方法的返回值分配内存时,并不会到驻留池中去检查是否有值为Hello的字符串已经存在了。所以自然不会让s2指向驻留池内的对象。
为了让编程者可以强制CLR检查驻留池,以避免冗余的字符串副本,String类的设计者提供了一个名为Intern的类方法。以下是该方法的一个演示样例:
StringBuilder sb = new StringBuilder();
sb.Append("He").Append("llo");string s1 = "Hello";
string s2 = String.Intern(sb.ToString());
bool same = (object) s1 == (object) s2;
好了,same又是true了。
Intern方法接受一个字符串作为參数,它会在驻留池中检查是否存在參数所表示的字符串。
假设存在,则返回那个驻留池中的字符串的引用。否则向驻留池中增加一个新的表示同样值的字符串。并返回这个字符串的引用。
只是要注意的是,就算Intern方法在驻留池中找到了同样值的字符串。也不能让您省却一次字符串内存分配的操作,由于作为參数的字符串已经被分配了一次内存了。
而使用Intern方法的优点在于,假设Intern方法在驻留池中找到了同样值的字符串。此时尽管在内存中存在两份该字符串的副本(一份是參数,一份是驻留池中的),可是随着时间的流逝。參数所引用的那个副本会被垃圾回收掉。这样对于该字符串内存中就不存在冗余了。
当您的程序中存在某个方法。能够依据不同的上下文环境创建并返回一个非常长的字符串,而在程序执行的过程中它有会常常返回相同的字符串时,您可能就要考虑考虑使用Intern方法来提高内存的利用率了。
只是相同值得注意的是,使用Intern方法让一个字符串存活于驻留池中也有一个副作用:即使已经不存在不论什么其他引用指向驻留池中的字符串了,这个字符串仍然不一定会被垃圾回收掉。也就是说即使驻留池中的字符串已经没实用处了,它可能也要等到CLR终结时才被销毁。
当您使用Intern方法的时候,也应该考虑到这个特殊的行为。