告诉一个不一样的.NETFramework字符串驻留
.NETFramework字符串驻留的机制实际上并没有我们想象的那么简单。下面我们就来通过一段代码的解读,来详细分析这一机制的相关概念。
.NETFramework在实际应用中,还是相当复杂的。我们要向熟练的运用这一架构来服务于我们的程序代码中。关于.NETFramework字符串驻留的机制,对于那些了解它的人肯定会认为很简单,但是我相信会有很大一部分人对它存在迷惑。在开始关于字符串的驻留之前,先给出一个有趣的Sample:
.NetFramework编码规范内容详解
全方位解读.NETFramework声明委托代码示
闲谈.NETFramework无接触部署方法
.NETFramework特点总结分析
.NETFramework事件处理相关概念详解
CodeSnip:
1staticvoidMain(string[]args)
2{
3stringstr1="ABCD1234";
4stringstr2="ABCD1234";
5stringstr3="ABCD";
6stringstr4="1234";
7stringstr5="ABCD"+"1234";
8stringstr6="ABCD"+str4;
9stringstr7=str3+str4;
10Console.WriteLine("stringstr1=
\"ABCD1234\";");
11Console.WriteLine("stringstr2=
\"ABCD1234\";");
12Console.WriteLine("stringstr3=
\"ABCD\";");
13Console.WriteLine("stringstr4=
\"1234\";");
14Console.WriteLine("stringstr5=
\"ABCD\"+\"1234\";");
15Console.WriteLine("stringstr6=
\"ABCD\"+str4;");
16Console.WriteLine("stringstr7=str3+str4;");
17Console.WriteLine("\nobject.Reference
Equals(str1,str2)={0}",object.
ReferenceEquals(str1,str2));
18Console.WriteLine("object.ReferenceEquals
(str1,\"ABCD1234\")={0}",object.
ReferenceEquals(str1,"ABCD1234"));
19Console.WriteLine("\nobject.Reference
Equals(str1,str5)={0}",object.
ReferenceEquals(str1,str5));
20Console.WriteLine("object.Reference
Equals(str1,str6)={0}",object.
ReferenceEquals(str1,str6));
21Console.WriteLine("object.ReferenceEquals
(str1,str7)={0}",object.ReferenceEquals
(str1,str7));
22Console.WriteLine("\nobject.ReferenceEquals
(str1,string.Intern(str6))={0}",object.
ReferenceEquals(str1,string.Intern(str6)));
23Console.WriteLine("object.ReferenceEquals
(str1,string.Intern(str7))={0}",object.
ReferenceEquals(str1,string.Intern(str7)));
24}
接下来我们来逐句地分析这段.NETFramework字符串驻留代码:
首先我们创建了两个完全相同的字符串(ABCD1234),并将他们分别赋予了两个字符创变量——str1和str2。然后把它们传给了object.ReferenceEquals。我们知道object.ReferenceEquals是用于确定两个变量是否具有相同的引用——换句话说,当两个变量引用的是同一块托管推的内存快的时候,返回True,否则返回False。
令我们感到奇怪的是,当我们分别创建的引用类型两个变量——string是引用类型。照理说CLR会在托管堆(ManagedHeap)中为它们分配两段内存快,他们不可能具有相同的引用才对,但是为什么object.ReferenceEquals方法会返回True呢。而对于第二个比较——一个字符串变量和一个和他具有相同内容的字符串("ABCD1234";)直接进行比较,按照我们对CLR内存的分配的一般理解,应该是CLR首先会在托管堆中为这段字符串("ABCD1234")分配内存快,然后把相对应的引用传递给object.ReferenceEquals方法(由于分配在托管堆的这段字符串并没有被任何变量引用,所以当垃圾回收的时候会被回收掉),所以无论如何也不应该返回True。
我们先把问题留到最后,接着分析我们的Sample。上面们对字符串变量之间以及变量与字符串之间进行了比较,如果我们对一个字符串变量和一个动态创建的字符串(通过+Operator把两个字符串连接起来)进行比较,结果又会如何呢?我们来看看下面的伪代码演示:
在上面的.NETFramework字符串驻留例子中,我们用三种不同的方式创建了3个字符串变量(str5,str6,str7)——string+string;string+variable;variable+variable。然后分别和我们已经创建的、和它们具有相同字符串“值”的变量(str1)作比较。同样令我们感到奇怪的是第一个返回True,而后两个则为False。带着这些疑惑我们来看看对于string这一特殊的类型说采用的特殊的使用机制。
1.System.String虽然是一个引用类型,但是它具有其自身的特殊性。我们最容易想到的是它创建的特殊性——一般的对象在创建的时候需要通过new关键字调用对应的构造函数来实现;而创建一段string不需要这么做——我们只需要把对应的字符换赋给给对应的字符串变量就可以了。
之所以存在着这种差异,是因为他们在创建过程中使用的IL指令时不同的——一般的引用对象的创建是通过newobj这样一个IL指令来实现的,而创建一个字符串变量的IL指令则是ldstr(loadstring)。(象C#,VB.NET这样的语言毕竟是高级语言,进行了高度的抽象,站在这样的角度分析问题往往不能够看到其实质,所以有时候我们把应该从交底层上面找突破口——比如分析IL,Metadata…);
2.由于String是我们做到频率最高的一种类型,CLR考虑性能的提升和内存节约上,对于相同的字符串,一般不会为他们分别分配内存块,相反地,他们会共享一块内存。CLR实际上采用这个的机制来实现的:CLR内部维护着一块特殊的数据结构——我们可以把它看成是一个Hashtable,这个Hashtable维护者大部分创建的string(我这里没有说全部,因为有特例)。这个Hashtable的Key对应的相应的string本身,而Value则是分配给这个string的内存块的引用。
当CLR初始化的时候创建这个Hashtable。一般地,在程序运行过程中,如果需要的创建一个string,CLR会根据这个string的HashCode试着在Hashtable中找这个相同的string,如果找到,则直接把找到的string的地址赋给相应的变量,如果没有则在托管堆中创建一个string,CLR会先在managedheap中创建该strng,并在Hashtable中创建一个Key-ValuePair——Key为这个string本身,Value位这个新创建的string的内存地址,这个地址最重被赋给响应的变量。这样我们就能解释上面.NETFramework字符串驻留的疑问了。
当创建str1的时候,CLR现在我们上面提到的Hashtable中找“ABCD1234”这样的一个string,没有找到,则在托管堆中为这个string分配一块内存,然后在Hashtable为该string添加一个Key-ValuePair。接着创建str2,CLR仍然会在Hashtable找ABCD1234这样的一个string,这回它会找到我们新创建的这个Entry,所以这个Key-ValuePair中Value(string的地址)会赋给str2。因为str1和str2具有相同的引用,所以调用object.ReferenceEquals返回True。同理当我们对str1和"ABCD1234"进行比较的时候,str1直接传入该方法,放传入"ABCD1234"这个字符串的时候,CLR同样会在Hashtable找ABCD1234这样的一个string,相同的Entry被找到,这个Entry(Key-ValuePair)的Value(string的地址)被传到object.ReferenceEquals,所以他们仍然相同的引用,结果返回True。
3.并非所有的情况下.NETFramework字符串驻留都会起作用。对于对一个动态创建的字符串(比如string+variable;variable+variable),这种驻留机制便不会起作用。因为对于这样的字符串,是不会被添加到内部的Hashtable中的。但是对于string+string则不同,因为当这样的语句被编译成IL的时候,编译器是先把结构计算出来,然后再调用ldstr指令——而对于string+variable;variable+variable这种情况,所对应的IL指令是Concat。所以对于string+string字符串的驻留仍然有效。
所以现在我们就可以解释第二个疑问了。
虽然对于对一个动态创建的字符串(比如string+variable;variable+variable),.NETFramework字符串驻留机制便不会起作用。但是我们可以手工的启用驻留机制——那就是调用定义的System.String中的静态方法Intern。这个方法接受一个字符串作为他的输入参数,返回的经过驻留处理的string。他的实现机制是:如果能在内部的HashTable中找到传入的string,则返回对应的string引用,否则就在HashTable添加该string对应的Entry,并返回string的引用。所以下面的代码就不难解释了。
相关推荐
【字符串驻留】字符串驻留是.NET Framework为了优化字符串操作而引入的一种机制。在.NET中,所有的字符串常量在编译时会被收集并存储在一个全局的哈希表中,称为字符串驻留池。当程序在运行时创建一个新字符串,如果...
这是老师做过的一个项目,抽取了当中核心之一的内容给大家试试,有些难度。 1、实验目的 ...看看这里:《告诉一个不一样的.NET Framework字符串驻留》,另外也可以找找对应.NET Framework底层实现的文章。
字符串内存驻留机制是指CLR在内存中维护一个字符串驻留池,池中存储着所有已经创建的字符串对象实例。这样,当我们创建一个新的字符串对象时,CLR会首先检查驻留池中是否已经存在该字符串,如果存在,就返回该字符串...
例如,链接表词典、位向量以及只包含字符串的集合。 System.ComponentModel:提供用于实现组件和控件的运行时和设计时行为的类。此命名空间包括用于属性和类型转换器的实现、数据源绑定和组件授权的基类和接口。 ...
Connection 类使用数据库连接字符串来连接数据库,该字符串是以“键\值”对的形式实现。 知识点 1.2: 命名空间 命名空间 System.Data.Common 包含由 .NET Framework 数据提供程序共享的类。.NET Framework 数据...
11. **字符串驻留池**:字符串是不可变的,C#使用字符串驻留池来优化内存使用,相同内容的字符串会共享同一个实例。`string s3 = string.Intern(s1 + s2);` 和 `string s4 = string.IsInterned(x1 + x2 + x3);` 展示...
在C#中,可以通过创建ManagementObject类的实例来实现,设置相应的IP地址、社区字符串(Community String,类似于认证凭证)和其他参数。 2. **OID(Object Identifier)**:OID是MIB中的唯一标识符,用于定位管理...
在C#中,你可以使用System.Text.StringBuilder类来构建OID字符串,使用System.Net.Sockets.Socket类进行网络通信。同时,你需要处理SNMP的请求/响应交互,以及错误处理和超时机制。对于复杂的SNMP操作,可能还需要...
ADO.NET是Microsoft为.NET Framework开发的一套数据库访问技术,它是一种用于连接和操作数据库的对象模型集合。相比于其前身ADO(ActiveX Data Objects),ADO.NET提供了更加高效、灵活的数据库交互方式。 **1.2 ...
SQLCE提供不同平台的支持,包括Windows和.NET Framework。 - 安装完成后,需要将SQLCE的动态链接库(DLLs)添加到Delphi的搜索路径中,以便程序运行时能够找到必要的库文件。 - 同时,需要在Delphi项目中包含SQLCE...
在这个例子中,它向客户端发送了一个简单的字符串响应。 4. **编译项目**: - 编译完成后,生成的`MyHandler.dll`文件将位于项目的`Bin\Debug`目录下。 #### 三、部署处理程序 1. **创建目录**: - 在`C:\...