字符串留用与字符串池

1、关于字符串操作对应用程序性能的影响

字符串相等性检查是应用程序常见的操作,于此同时,这也是一种严重损害性能的操作.执行序号(字符串的二进制)相等行检查时,CLR会进行以下操作:

1、判断字符串的长度是否相等,不相等,比较结果直接返回false,如果相等,继续下一步操作

2、比较字符串的长度相等,CLR会比较每个单独的字符才能最终确定。而执行对语言文化敏感的比较时,CLR必须比较所   有单独的字符,因为字符串即使长度不同也可能相等.

 

2、字符串留用  一  减少复制相同字符串实例对内存的消耗

因为字符串的不可变性,如果应用程序经常对字符串进行区分大小写的序号比较,这个时候如果你知道有许多字符串会有相同的值,那么就可以利用CLR的"字符串留用"机制来提升应用程序的性能.

原理:只保存相同字符串的一个实例来提升内存的利用率。将相同的字符串变量引用都指向一个字符串对象.

 

3、CLR实现字符串留用的过程

CLR初始化时会创建一个内部哈希表.在这个表中,键(key)是字符串,而值(value)是对托管堆中的String对象的引用.这个过程类似与四、CLR执行程序集中代码和IL代码简介 CLR第一次执行一个方法的过程类似,它会初始化一个内部结构,生成一系列的地址,地址指向JITComliler函数,该函数会将代码转成CPU指令等操作,并返回结果给调用的C#方法.

注:该哈希表最开始是空的.

 

String类提供了两个方法便于你访问这个内部哈希表:

(1)、Intern方法用于获取一个String,获得它的哈希码,并在哈希表中检查是否有相匹配的,如果存在完全相同的字符串,就返回对现有String对象的应用.如果不存在全完相同的字符串,就创建字符串的副本.将副本添加到内部哈希表中,返回对该副本的引用.如果应用程序不再保持对原始String对象的引用,这时垃圾回收器就会介入,将字符串的内存强行释放掉.

注:垃圾回收器不会释放内部哈希表引用的字符串,因为哈希表正在容纳对它们的引用.除非卸载AppDomain或进程终止,否则其内部哈希表应用的String对象不能被释放.

 

(2)IsInterned方法也获取一个String,并在内部哈西表中查找它.如果哈西表中有匹配的字符串,IsInterned方法就返回对这个留用字符串对象的应用.但如果没有,IsInterned就返回null,不会将字符串添加到哈希表中.

 

4、CLR默认留用程序集元数据中的字面值字符串

程序集加载时,CLR默认留用程序集元数据中的描述的所有字面值字符串,大微软知道这个过程可能因为额外的哈希表查找而显著影响性能,所以现在可以禁用此功能.通过对程序集用System.RunTime.ComiplerServices.CompilationRelaxationsAttribute进行了标记,并指定了System.RunTime.ComiplerServices.CompilationRelaxations.NoStringIntering标志值.那么根据ECMA规范,CLR可能选择不留用指定程序集的元数据定义的所有字符串.为了提升性能,C#编译器在编译程序集是总是指定上述连个特性和标志.

 

5、CLR的4.5班版本及以上选择忽略4中的特性和标志,及显示留用指定字符串

由于CLR4.5及以上选择忽略4中的特性,所以程序集加载到AppDomain中时,CLR会对该程序集中元数据中所描述的所有字面值字符串.代码如下:

String str = "xiaochao";
String str1 = "xiaochao";
Console.WriteLine(ReferenceEquals(str,str1));//输出:True

注:程序集加载到AppDomian中时,CLR对程序集中的元数据中的字面值字符串进行了留用,所以导致了"xiaochao"被留用,结果str和str1引用了堆中的同一个"xiaochao"字符串,但是我们的代码不能依赖这一行为,因为未来的CLR版本可能会重视这些特性和标志,到时候将不会对程序集元数据中的字面值字符串不进行留用.下面的代码将显示留用字符串,代码如下:

//去内部哈希表中检查是否有xiaochao字符串,有的话返回该字符串的引用,反之,创建该字符串的副本,返回该副本的引用.
str = String.Intern(str);
//去内部哈希表中检查是否有xiaochao字符串,发现有xiaochao字符串,返回它的引用
str1 = String.Intern(str1);
Console.WriteLine(ReferenceEquals(str, str1));//输出:True

 

6、字符串池

编译源代码时,编译器必须处理每个字面值字符串,并在托管模块中的元数据中嵌入.同一个字符串在源代码中多次出现,如果每次都去内存中重复开辟空间,不仅浪费内存,而且把它们嵌入元数据会使生成的文件无谓的增大.

为了解决这个问题,许多编译器(包括C#编译器)只在模块的元数据中只将字面值字符串至写入一次,CLR默认留用程序集元数据中的字面值字符串。引用改字符串的所有代码都被修改成引用元数据中的同一个字符串.编译器将单个字符串的多个实例合并成一个实例,能显著减少模块的大小.C/C++编译器多年来一直采用这个技术,这个技术被称为"字符串池".

posted @ 2018-05-27 04:26  郑小超  阅读(818)  评论(1编辑  收藏  举报