探讨一下.NET字串拘留池
XiaoMing在博客园上发表的《年前的面试经历(二) 》中回贴众多,其中谈到一个面试题:string a = "a" + "b"到底分配几次内存。
这涉及到CLR内部的字串拘留池(string interning pool)问题。
网友 Ivony和 横刀天笑的回贴引用资料指出拘留池是进程范围内的,因此,有可能以下定义字串常量的代码不会导致分配两次内存——因为另一个进程可能已经在拘留池中创建了“a”或 “ab”这两个字串对象。
string s1 = "a";
string s2 = "a";
string s3 = "a" + "b";
Console.WriteLine(s1);
Console.WriteLine(s2);
Console.WriteLine(s3);
我对此结论有点怀疑,于是到Google和MSDN中查找,发现字串拘留池的有关资料很混乱。最后决定自己编程作实验。
String类有一个IsInterned()方法用于检测一个字串是否在拘留池中,另一个Intern()方法用于将一个字串加入拘留池中。
为此,我写了以下测试代码:
class Program
{
//static string outerstr = "j";
static void Main(string[] args)
{
string str = new string('j',1); //动态构建的字串,不会放到拘留池中
if (string.IsInterned(str) == null)
Console.WriteLine(str + " is not interned"); //不在拘留池
else
Console.WriteLine(str+ " is interned"); //在拘留池
Console.ReadKey();
string s = string.Intern(str); //强制加入拘留池
//再次检测
if (string.IsInterned(str) == null)
Console.WriteLine(str + "is not interned");
else
Console.WriteLine(str+ " is interned");
Console.ReadKey();
}
上述代码运行结果如下:
j is not interned
j is interned
不管你运行多少次,也不管你是否同时运行多个此程序的实例,始终结果是一致的,都是上面的结果。这说明进程结束后,字串拘留池中的与此进程所装载的程序集相关的字串常量对象被清除。
现在取消对outerstr 变量的注释,结果变为:
j is interned
j is interned
这说明程序集中的常量“j”在装载时被加入到了字串拘留池中,所以才有上述结果。
还有一个问题,字串拘留池中的对象能否跨越不同进程边界共享?
编写另一个测试程序:
class Program
{
static void Main(string[] args)
{
string str = new string('j', 1);
Console.WriteLine(string.IsInterned(str)==null);
Console.ReadKey();
}
}
不管前一个测试程序是否在运行,上述代码始终输出true,说明“j”这个字串没有在拘留池中,此进程无法获取另一个进程追加到字串拘留池中的“j”字串。
从这些实验是否可以得出以下结论?
当进程运行结束,此进程所加载的程序集中所定义的字串常量对象会被CLR从字串拘留池中移除。
因此,CLR字串拘留池中的字串常量是“进程和程序集相关”的。
应用程序所定义的字串常量对象在应用程序域装载程序集时被加入到字串拘留池中。
所以,字串拘留池中的字串对象不能跨进程共享。不然,我们如何解释代码运行的结果?
由于同一进程中可以创建多个应用程序域,我还没有编写代码测试字串拘留池中的字串对象是否可以在属于同一进程的多个应用程序域共享。此问题留待进一步探索。
有无高人能彻底解释清楚这一问题?
补充:
我的测试环境是Windows 7 + VS2010 RC.