Literal String的特性

提出问题:

下列代码的结果是True还是False?
static void Main() 

 string s = "Test"; 
 string t = "Test"; 
 Console.WriteLine(Object.ReferenceEquals(s, t)); 
}

按照直觉,他的结果应该是False,因为我们定义的是两个字符串,然后比较它们的引用。虽然他们的内容相同,但他们的引用应该不同。但运行结果告诉我——我的直觉是错误的。
他的结果为什么True?

String Pooling:

我们先来看个称之为String Pooling的技术。
当编译器编译源代码时,它必须将所有的Literal String编译到结果文件的metadata当中。如果相等的字符串常量出现多次,而编译器只是简单的将它们放到结果文件当中,无疑会加大结果文件的体重。为了赶上现在流行的瘦身潮流,C#编译器决定合并所有相等的Literal String。这就是String Pooling技术。这种技术的使用其实是由来已久,甚至String Pooling这个名字还是MC++的称呼。

String Interning:

对于刚提出的问题,说到这里就已经解释清楚了。但对于不同的dll中的相等literal string会怎样?抱着这个疑问我写了个小测试程序。

-------------------------------------------------------
文件名: Module1.cs
编译指令: csc /out:module1.dll /t:library module1.cs

using System;
public class Module1
{
   public const object CS = "Test";
}

-------------------------------------------------------

文件名: Module2.cs
编译指令: csc /out:module2.dll /t:library module2.cs

using System;
public class Module2
{
   public const object CS = "Test";
}

-------------------------------------------------------

文件名: Main.cs
编译指令: csc  /r:module1.dll;module2.dll Main.cs

using System;
class MainClass
{
  static void Main()
  {
  Console.WriteLine(Object.ReferenceEquals(Module2.CS, Module1.CS));
  Console.Read();
  }
}

编译运行,居然结果也是 True!这是我刚才说错了吗?不,对于刚才的问题我们的答案并没有错。至于不同的dll中的literal string为什么会也有相同的引用。这就是另外一个原因。
我们知道在.net当中我们编译的dll和exe不是机器码而是IL。这些Il的执行还需要JIT编译器将他们编译成机器码。C#编译器为了给文件减肥使用了String Pooling。难道我们的JIT编译器就会这么甘于平庸吗。考虑到String的相等比较,需要一个一个字母的比较来得出结果,对literal string也这样,未免也太不积极了。为了提高效率我们的JIT编译器也使用了类似的技术。
对于literal string,他将它存到一个hash table(称之为Interning table)。自然,不同模块的相同的字符串也就合并到了一个地方。再对他们进行相等比较就是一个引用的比较就OK了,性能得到了极大的提高。当然,对于动态构建在heap中的字符串就没有这个好处了。
String提供了一个IsInterned静态方法。String.IsInterned ()方法的作用是在Interning table中查找指定的字符串,如果找到的相等的字符串就返回该字符串的引用,如果没有相等的字符串就返回null。具体解释可以查阅MSDN。

C#对Interning table的利用:

我们来看看C#相对于C++,Java独有的针对string的witch语句。

switch(s)
{
  case "hello":
  break;

  case "hi":
  break;

  default:
  break;
}

我们case语句中的字符串都是literal string,JITer会将他们放到Interning table中。对于s和他们的相等比较,我们就不用一个一个字母的找了。执行String.IsInterned(s),在Interning table寻找s:返回null,说明在Interning table都没有与s相等的字符串,这就跟不要说我们在case中列出的这几个了,此时自然是直接转到default;如果返回non-NUll,则将返回的这个引用和我们cese中列出的字符查串的引用进行比较。这种比较就是纯粹的引用比较,相当于int数据的比较,效率很高。
以前我老担心switch(string)的效率,看来是杞人忧天了

结论:
对于我们提出的问题,应该是C#编译器的String Pooling特性的功劳。
但对于不具备String Pooling的编译器,他的结果也应该是True,因为还有我们的Common Runtime来保证。

参考资料:<Applied MS .Net Framework Programming >节选

posted on 2004-03-25 09:53  Meyer  阅读(2201)  评论(0)    收藏  举报

导航