字符串的驻留(String Interning)
http://www.cnblogs.com/artech/archive/2007/03/04/663728.html
关于字符串的驻留的机制,对于那些了解它的人肯定会认为很简单,但是我相信会有很大一部分人对它存在迷惑。在开始关于字符串的驻留之前,先给出一个有趣的Sample:
Code Snip:
{
string str1 = "ABCD1234";
string str2 = "ABCD1234";
string str3 = "ABCD";
string str4 = "1234";
string str5 = "ABCD" + "1234";
string str6 = "ABCD" + str4;
string str7 = str3 + str4;
Console.WriteLine("string str1 = \"ABCD1234\";");
Console.WriteLine("string str2 = \"ABCD1234\";");
Console.WriteLine("string str3 = \"ABCD\";");
Console.WriteLine("string str4 = \"1234\";");
Console.WriteLine("string str5 = \"ABCD\" + \"1234\";");
Console.WriteLine("string str6 = \"ABCD\" + str4;");
Console.WriteLine("string str7 = str3 + str4;");
Console.WriteLine("\nobject.ReferenceEquals(str1, str2) = {0}", object.ReferenceEquals(str1, str2));
Console.WriteLine("object.ReferenceEquals(str1, \"ABCD1234\") = {0}", object.ReferenceEquals(str1, "ABCD1234"));
Console.WriteLine("\nobject.ReferenceEquals(str1, str5) = {0}", object.ReferenceEquals(str1, str5));
Console.WriteLine("object.ReferenceEquals(str1, str6) = {0}", object.ReferenceEquals(str1, str6));
Console.WriteLine("object.ReferenceEquals(str1, str7) = {0}", object.ReferenceEquals(str1, str7));
Console.WriteLine("\nobject.ReferenceEquals(str1, string.Intern(str6)) = {0}", object.ReferenceEquals(str1, string.Intern(str6)));
Console.WriteLine("object.ReferenceEquals(str1, string.Intern(str7)) = {0}", object.ReferenceEquals(str1, string.Intern(str7)));
}
下边是输出的结果:
接下来我们来逐句地分析这段代码:
首先我们创建了两个完全相同的字符串(ABCD1234),并将他们分别赋予了两个字符创变量——str1和str2。然后把它们传给了object.ReferenceEquals。我们知道object.ReferenceEquals是用于确定两个变量是否具有相同的引用——换句话说,当两个变量引用的是同一块托管推的内存快的时候,返回True,否则返回False。
string str2 = "ABCD1234";
object.ReferenceEquals(str1, str2)= True;
object.ReferenceEquals(str1, "ABCD1234")) = True;
令我们感到奇怪的是,当我们分别创建的引用类型两个变量——string是引用类型。照理说CLR会在托管堆(Managed
Heap)中为它们分配两段内存快,他们不可能具有相同的引用才对,但是为什么object.ReferenceEquals
方法会返回True呢。而对于第二个比较——一个字符串变量和一个和他具有相同内容的字符串("ABCD1234";)直接进行比较,按照我们对CLR内存的分配的一般理解,应该是CLR首先会在托管堆中为这段字符串("ABCD1234")分配内存快,然后把相对应的引用传递给object.ReferenceEquals方法(由于分配在托管堆的这段字符串并没有被任何变量引用,所以当垃圾回收的时候会被回收掉),所以无论如何也不应该返回True。
我们先把问题留到最后,接着分析我们的Sample。上面们对字符串变量之间以及变量与字符串之间进行了比较,如果我们对一个字符串变量和一个动态创建的字符串(通过+Operator把两个字符串连接起来)进行比较,结果又会如何呢?我们来看看下面的伪代码演示:
string str4 = "1234";
string str5 = "ABCD" + "1234";
string str6 = "ABCD" + str4;
string str7 = str3 + str4;
object.ReferenceEquals(str1, str5) = True
object.ReferenceEquals(str1, str6) = False
object.ReferenceEquals(str1, str7)) = False
在上面的例子中,我们用三种不同的方式创建了3个字符串变量(str5,str6,str7)——string+string;string+variable;variable+variable。然后分别和我们已经创建的、和它们具有相同字符串“值”的变量(str1)作比较。同样令我们感到奇怪的是第一个返回True,而后两个则为False。带着这些疑惑我们来看看对于string这一特殊的类型说采用的特殊的使用机制。
1. System.String虽然是一个引用类型,但是它具有其自身的特殊性。我们最容易想到的是它创建的特殊性——一般的对象在创建的时候需要通过new关键字调用对应的构造函数来实现;而创建一段string不需要这么做——我们只需要把对应的字符换赋给给对应的字符串变量就可以了。之所以存在着这种差异,是因为他们在创建过程中使用的IL指令时不同的——一般的引用对象的创建是通过newobj这样一个IL指令来实现的,而创建一个字符串变量的IL指令则是ldstr (load string)。(象C#,VB.NET这样的语言毕竟是高级语言,进行了高度的抽象,站在这样的角度分析问题往往不能够看到其实质,所以有时候我们把应该从交底层上面找突破口——比如分析IL,Metadata…);
2.
由于String是我们做到频率最高的一种类型,CLR考虑性能的提升和内存节约上,对于相同的字符串,一般不会为他们分别分配内存块,相反地,他们会共享一块内存。CLR实际上采用这个的机制来实现的:CLR内部维护着一块特殊的数据结构——我们可以把它看成是一个Hash
table,这个Hash table维护者大部分创建的string(我这里没有说全部,因为有特例)。这个Hash
table的Key对应的相应的string本身,而Value则是分配给这个string的内存块的引用。当CLR初始化的时候创建这个Hash
table。一般地,在程序运行过程中,如果需要的创建一个string,CLR会根据这个string的Hash Code试着在Hash
table中找这个相同的string,如果找到,则直接把找到的string的地址赋给相应的变量,如果没有则在托管堆中创建一个string,CLR会先在managed
heap中创建该strng,并在Hash table中创建一个Key-Value
Pair——Key为这个string本身,Value位这个新创建的string的内存地址,这个地址最重被赋给响应的变量。这样我们就能解释上面的疑问了。
string str2 = "ABCD1234";
object.ReferenceEquals(str1, str2)= True;
object.ReferenceEquals(str1, "ABCD1234")) = True;
当创建str1的时候,CLR现在我们上面提到的Hash
table中找“ABCD1234”这样的一个string,没有找到,则在托管堆中为这个string分配一块内存,然后在Hash
table为该string添加一个Key-Value Pair。接着创建str2,CLR仍然会在Hash
table找ABCD1234这样的一个string,这回它会找到我们新创建的这个Entry,所以这个Key-Value
Pair中Value(string的地址)会赋给str2。因为str1和str2
具有相同的引用,所以调用object.ReferenceEquals返回True。同理当我们对str1和"ABCD1234"进行比较的时候,str1直接传入该方法,放传入"ABCD1234"这个字符串的时候,CLR同样会在Hash
table找ABCD1234这样的一个string,相同的Entry被找到,这个Entry(Key-Value
Pair)的Value(string的地址)被传到object.ReferenceEquals,所以他们仍然相同的引用,结果返回True。
3.
并非所有的情况下字符串的驻留都会起作用。对于对一个动态创建的字符串(比如string+variable;variable+variable),这种驻留机制便不会起作用。因为对于这样的字符串,是不会被添加到内部的Hash
table中的。但是对于string+string则不同,因为当这样的语句被编译成IL的时候,编译器是先把结构计算出来,然后再调用ldstr指令——而对于string+variable;variable+variable这种情况,所对应的IL指令是Concat。所以对于string+string字符串的驻留仍然有效。
比如对于以下一段代码:
{
string str1 = "ABC";
string str2 = str1 + "123";
string str3 = "ABC" + "123";
}
对应的IL Code是:
{
.entrypoint
// Code size 26 (0x1a)
.maxstack 2
.locals init ([0] string str1,
[1] string str2,
[2] string str3)
IL_0000: nop
IL_0001: ldstr "ABC"
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldstr "123"
IL_000d: call string [mscorlib]System.String::Concat(string,
string)
IL_0012: stloc.1
IL_0013: ldstr "ABC123"
IL_0018: stloc.2
IL_0019: ret
} // end of method Program::Main
所以现在我们就可以解释第二个疑问了。
虽然对于对一个动态创建的字符串(比如string+variable;variable+variable),驻留机制便不会起作用。但是我们可以手工的启用驻留机制——那就是调用定义的System.String中的静态方法Intern。这个方法接受一个字符串作为他的输入参数,返回的经过驻留处理的string。他的实现机制是:如果能在内部的Hash
Table中找到传入的string,则返回对应的string引用,否则就在Hash
Table添加该string对应的Entry,并返回string的引用。所以下面的代码就不难解释了。
Console.WriteLine("\nobject.ReferenceEquals(str1, string.Intern(str6)) = {0}", object.ReferenceEquals(str1, string.Intern(str6)));
Console.WriteLine("object.ReferenceEquals(str1, string.Intern(str7)) = {0}", object.ReferenceEquals(str1, string.Intern(str7)));
字符串的驻留(String Interning)的更多相关文章
- 什么是string interning(字符串驻留)以及python中字符串的intern机制
Incomputer science, string interning is a method of storing only onecopy of each distinct string val ...
- Python string interning原理
原文链接:The internals of Python string interning 由于本人能力有限,如有翻译出错的,望指明. 这篇文章是讲Python string interning是如何 ...
- [JAVA]字符串常量池String pool
字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定.不仅如此,还可以使用 String 的 intern() 方法在运行过程中将 ...
- The internals of Python string interning
JUNE 28TH, 2014Tweet This article describes how Python string interning works in CPython 2.7.7. A fe ...
- 字符串的使用(string,StringBuffer,StringBuilder)
String中==与equals的区别:==比较字符串中的引用相等equals比较字符串中的内容相等(因为字符串有重写equals方法) string常用的方法 返回类型 方法 操作功能 Char c ...
- Swift_字符串详解(String)
Swift_字符串详解(String) 类型别名 //类型别名 fileprivate func testTypeAliases() { let index = String.Index.self p ...
- C# 字符串比较大小 string.Compare()方法
string.Compare方法,用来比较2个字符串值得大小 string.Compare(str1, str2, true); 返回值: 1 : str1大于str2 0 : str1等于str2 ...
- C风格字符串和C++ string 对象赋值操作的性能比较
<<C++ Primer>> 第四版 Exercise Section 4.3.1 部分Exercise 4.2.9 习题如下: 在自己本机执行如下程序,记录程序执行时间: # ...
- C风格字符串和C++string对象的相互转化
一.C风格的字符串转化为C++的string对象 C++中,string 类能够自动将C 风格的字符串转换成string 对象 #include <iostream> #include ...
随机推荐
- 使用ASP.NET Core实现Docker的HealthCheck指令
写在前面 HealthCheck 不仅是对应用程序内运行情况.数据流通情况进行检查, 还包括应用程序对外部服务或依赖资源的健康检查. 健康检查通常是以暴露应用程序的HTTP端点的形式 实施,可用于配 ...
- npm ERR! Cannot read property 'match' of undefined 错误处理
跟往常一样运行npm install 的时候,突然报错.错误情况如下: npm ERR! Cannot read property 'match' of undefined npm ERR! A co ...
- 2014-11-1 NOIP模拟赛1
冲刺NOIP2014复赛模拟题第六套第二试 题目名称 日历游戏 最大公约数 密码 英文代号 calendar gcd pasuwado 输入文件名 calendar.in gcd.in pasuw ...
- JS实现简单时钟效果
老师上课需要我们做一个时钟的小作业 ,我把它放在上面记录一下啦 表盘和时针我都是用的背景图的形式,然后绝对定位,通过调整left和top确定时针.分针.秒针的位置,transform-origin设置 ...
- python爬虫——web前端基础(3)
超链接的使用------>>>> 链接的引用使用的是<a>标记. <a>标记的基本语法:<a href="链接地址" ta ...
- C# dynamic与var的区别
1.C#编程总结(十四)dynamic 2.var和dynamic的区别及如何正确使用dynamic?
- Python内建函数一
内建函数 1. abs(number) 用法:返回数字的绝对值 2. all(iterable) 用法:如果iterable的所有元素都是真值,就返回True,否则返回False 3. any(ite ...
- Jenkins权威指南
https://jenkins.io/doc/ ---官网 https://www.cnblogs.com/leefreeman/p/4226978.html
- LWIP应用指南学习。
一 TCP接口函数:tcp_init() 必须在调用其它TCP函数之前调用,必须用一个硬件定时器来配置每TCP_FAST_INTERVAL (ms)调用一次tcp_fasttmr() :每TCP_SL ...
- AD中添加中文字符丝印的方法:
一 一般中文丝印: 用快捷键L打开层管理,在View options中勾选convert special 选项: 用快捷键P,S文本中输入你要的汉字,选中ture type,在select ture ...