C#中拘留池问题【转载】

什么是拘留池

string类型是C#中一个很特殊的类型。看上去象值类型,事实上确实引用类型。

string中有一个概念是拘留池。了解它更有利于我们更好的使用string类型。

1, 什么是拘留池?

  公共语言运行库会自动维护一个名为“拘留池”(intern pool) 的表,它包含在程序中声明的每个唯一字符串常数的单个实例,以及以编程方式添加的 String 的任何唯一实例。

该拘留池节约字符串存储区。如果将字符串常数分配给几个变量,则每个变量设置为引用“拘留池”(intern pool) 中的同一常数,而不是引用具有相同值的 String 的几个不同实例。

此方法在拘留池中查找 str。如果已经将 str 放入拘留池中,则返回对此实例的引用;否则返回空引用(在 Visual Basic 中为 Nothing)。

2, string类型变量值修改后,实际上是在内存中新创建了一个空间。而不是在原有值上的修改。

  理解了拘留池的概念,对这句话就更好理解。因为一个“中国人”的值可能有多个string类型变量指向该值。

当其中一个变量改变“中国人”为“中国人民”时,公共语言运行库会重新给“中国人民”分配一块空间,而不是修改原来的“中国人”。因为修改原来的值会影响其它指向该值的变量。

本文将从.NET字符串拘留池讲起,希望大家在今后碰到这样的问题时,能正确处理。公共语言运行库通过维护一个表来存放字符串,该表称为拘留池,

它包含程序中以编程方式声明或创建的每个唯一的字符串的一个引用.因此,具有特定值的字符串的实例在系统中只有一个。

 

  XiaoMing在博客园上发表的《年前的面试经历(二) 》中回贴众多,其中谈到一个面试题:string a = "a" + "b"到底分配几次内存。

  这涉及到CLR内部的.NET字符串拘留池(string interning pool)问题。

  网友 Ivony和 横刀天笑的回贴引用资料指出拘留池是进程范围内的,因此,有可能以下定义字串常量的代码不会导致分配两次内存——因为另一个进程可能已经在拘留池中创建了“a”或 “ab”这两个字串对象。

 

View Code
string s1 = "a"; 
string s2 = "a"; 
string s3 = "a" + "b"; 
Console.WriteLine(s1); 
Console.WriteLine(s2); 
Console.WriteLine(s3);

 

 

  我对此结论有点怀疑,于是到Google和MSDN中查找,发现.NET字符串拘留池的有关资料很混乱。最后决定自己编程作实验。

  String类有一个IsInterned()方法用于检测一个字串是否在拘留池中,另一个Intern()方法用于将一个字串加入拘留池中。

  为此,我写了以下测试代码:

View Code
 1  class Program
 2     {
 3         //static string outerstr = "j";
 4         static void Main(string[] args)
 5         {
 6             string str = new string('j', 1); //动态构建的字串,不会放到拘留池中 
 7             if (string.IsInterned(str) == null)
 8                 Console.WriteLine(str + " is not interned"); //不在拘留池 
 9             else
10                 Console.WriteLine(str + " is interned"); //在拘留池
11             Console.ReadKey();
12             string s = string.Intern(str); //强制加入拘留池 
13             //再次检测 
14             if (string.IsInterned(str) == null)
15                 Console.WriteLine(str + "is not interned");
16             else
17                 Console.WriteLine(str + " is interned");
18             Console.ReadKey();
19         }
20     }

 

上述代码运行结果如下:

  j is not interned
  j is interned


  不管你运行多少次,也不管你是否同时运行多个此程序的实例,始终结果是一致的,都是上面的结果。这说明进程结束后,字串拘留池中的与此进程所装载的程序集相关的字串常量对象被清除。

  现在取消对outerstr 变量的注释,结果变为:


  j is interned
  j is interned


  这说明程序集中的常量“j”在装载时被加入到了字串拘留池中,所以才有上述结果。

  还有一个问题,字串拘留池中的对象能否跨越不同进程边界共享?

  编写另一个测试程序: 

View Code
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 + Visual Studio 2010 RC.

posted @ 2012-05-05 18:36  Junsy  阅读(252)  评论(0)    收藏  举报