string 驻留机制

 
           string s1 = "abc";
           string s2 = "ab";
           string s3 = s2 + "c";
           string s4="abc";           string s5="abcd";
//求两个object的引用是否相等
 Console.WriteLine(object.ReferenceEquals(s1,s3));//false
 Console.WriteLine(object.ReferenceEquals(s1,s4));//true

//求两个object的值是否相等Console.WriteLine(object.Equals(s1, s3));//trueConsole.WriteLine(object.Equals(s1, s4));//true

Console.WriteLine(string.Equals(s1, s3));//trueConsole.WriteLine(string.Equals(s1, s4));//true

Console.WriteLine(string.ReferenceEquals(s1, s3));//falseConsole.WriteLine(string.ReferenceEquals(s1, s4));//true

//比较两个字符的大小Console.WriteLine(string.Compare(s1, s2));//1Console.WriteLine(string.Compare(s1, s4));//0Console.WriteLine(string.Compare(s1, s5));//-1

在.Net中处理字符串时,有一个很重要的机制,叫做字符串驻留机制。
由于string是编程中用到的频率较高的一种类型,CLR对相同的字符串,
只分配一次内存。CLR内部维护着一块特殊的数据结构,我们叫它字符串池,
可以把它理解成是一个HashTable,这个HashTable维护着程序中用到的一部分字符串,
HashTable的Key是字符串的值,而Value则是字符串的内存地址。一般情况下,
程序中如果创建一个string类型的变量,
CLR会首先在HashTable遍历具有相同Hash Code的字符串,
如果找到,则直接把该字符串的地址返回给相应的变量,
如果没有才会在内存中新建一个字符串对象。


 
 
 
 
 
 
 
 

字符串的驻留(String Interning)

 

关于字符串的驻留的机制,对于那些了解它的人肯定会认为很简单,但是我相信会有很大一部分人对它存在迷惑。在开始关于字符串的驻留之前,先给出一个有趣的Sample:

Code Snip:

static void Main(string[] args)
        {
            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 str1 = "ABCD1234";
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 str3 = "ABCD";
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 str1 = "ABCD1234";
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字符串的驻留仍然有效。

比如对于以下一段代码:

  static void Main(string[] args)
        {
            string str1 = "ABC";
            string str2 = str1 + "123";
            string str3 = "ABC" + "123";
}

对应的IL Code是:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .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)));

相关内容:
[原创]如何改Managed Code的Performance和Scalability系列之二:深入理解string和如何高效地使用string

string 驻留机制的更多相关文章

  1. C#入门篇6-8:字符串操作 深入研究字符串的内存驻留机制

    //字符串的内存驻留机制 public static void Test() { //当有多个字符串变量包含了同样的字符串实际值时, //CLR可能不会为它们重复地分配内存,而是让它们统统指向同一个字 ...

  2. python代码块,小数据池,驻留机制深入剖析

    一,什么是代码块. 根据官网提示我们可以获知: 根据提示我们从官方文档找到了这样的说法: A Python program is constructed from code blocks. A blo ...

  3. python 的字符串驻留机制

    我们都知道python中的引用计数机制,相同对象的引用其实都是指向内存中的同一个位置,这个也叫做“python的字符串驻留机制”.其他的就不多说了,自行研究. 重点!!!!!! python的引用计数 ...

  4. Python_驻留机制

    #coding=utf-8 #coding:utf-8 #- * -coding:utf-8 - * - '''以上为注明字符串的编码格式''' #驻留机制 '''Python支持短字符串驻留机制,对 ...

  5. python intern(字符串驻留机制)

    python 中为了提高字符串的使用用效率和节约内存,对于由 字母.数字.下划线组成的标识符采用了 intern 机制,即对于短字符串,将其赋值给多个对象时,内存中只有一个副本,多个对象共享这个副本. ...

  6. Python的驻留机制(仅对数字,字母,下划线有效)

    Python的驻留机制及为在同一运行空间内,当两变量的值相同,则地址也相同. 举例: a = 'abc' b = 'abc' print(id(a)) print(id(b)) 以上示例为驻留机制有效 ...

  7. 非常简单的string驻留池,你对它真的了解吗

    昨天看群里在讨论C#中的string驻留池,炒的火热,几轮下来理论一堆堆,但是在证据提供上都比较尴尬.虽然这东西很基础,但比较好的回答也不是那么容易,这篇我就以我能力范围之内跟大家分享一下 一:无处不 ...

  8. C#中的string驻留池

    刚开始学习C#的时候,就听说CLR对于String类有一种特别的内存管理机制:有时候,明明声明了两个String类的对象,但是他们偏偏却指向同一个实例.如下: String s1 = "He ...

  9. python学习之内存驻留机制简述

    第四章 4.1 小数据池 4.1.1 代码块 一个模块,一个函数,一个类,甚至一个command命名都可以称之为一个代码块. 官方解释: A Python program is constructed ...

随机推荐

  1. 设计模式--观察者模式--python

    观察者模式: 对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新. 主要解决: 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面.将这二者封装在 ...

  2. 【React自制全家桶】六、React性能优化(持续更新总结)

    一.通过虚拟DOM来提升性能(自动) 底层讲解见[React自制全家桶]二.分析React的虚拟DOM和Diff算法   二.将多次setState合并为一次执行(自动) 底层讲解见[React自制全 ...

  3. Django之缓存配置

    01-什么是缓存 缓存(cache),其作用是缓和较慢存储的高频次请求,简单来说,就是加速满存储的访问效率. 02-几种缓存配置 # 内存缓存:local-memory caching CACHES ...

  4. ASP.NET Core开发者指南(转发)

    ASP.NET Core开发者指南 2019年ASP.NET Core开发者指南: 你可以在下面找到一张图,该图展示了你可以选取的路径及你想学习的库,从而成为一名 ASP.NET Core 开发者.& ...

  5. lab 颜色模式的生理原因 黄色, 洋红色 刺眼。 绿色,蓝色,不刺眼。

    hsb 颜色模式理解了. lab 颜色模式,都说是生理原因.没说是啥生理原因. 猜测:黄色, 洋红色 刺眼.   绿色,蓝色,不刺眼. https://blog.csdn.net/self_mind/ ...

  6. 正说PropertyValuesProvider的应用

    Github地址:https://github.com/andyslin/spring-ext 编译.运行环境:JDK 8 + Maven 3 + IDEA + Lombok spring-boot: ...

  7. webdriervAPI(XPath元素定位)

    from  selenium  import  webdriver driver  =  webdriver.Chorme() driver.get("http://www.baidu.co ...

  8. python selenium API 常用方法

    配置使用环境 下载相应的浏览器驱动, Firefox 是默认的 本文以 chrome 为主 ,放在scripts目录下ChromeDriver 官方下载地址 : 所有版本的 ChromeDriver ...

  9. flask_script

    Flask Script扩展提供向Flask插入外部脚本的功能,包括运行一个开发用的服务器,一个定制的Python shell,设置数据库的脚本,cronjobs,及其他运行在web应用之外的命令行任 ...

  10. Linux的桌面环境gnome、kde、xfce、lxde 等等使用比较

    如果不是加入了图形界面,微软的Windows系列操作系统不会成功地占领计算机桌面这块高地.这种人机交换的图形化界面,使得界面更加直观.简易.而且更人性化,同时也大大减少了使用者的认知负担,普通用户无需 ...