江枫

C#String探寻

1.String的定义

   String是一个不可变的连续16位的Unicode代码值的集合,它直接派生自System.Object类型。

2.String的特性
  • 由于String类型直接派生于Object,所以它是引用类型,那就意味着String对象的实例总是存在于堆上。
  • String具有不变性,也就是说一旦初始化,它的值将永远不变。
  • String类型是封闭的,换言之,你的任何类型不能继承String。
  • 定义字符串实例的关键字string只是System.String 类型的一个映射。
    3.字符串驻留机制

       CLR会在初始化的时候创建一个内部的哈希表,key是字符串,value就是字符串在托管堆上的引用。而且字符串的驻留是基于整个进程的,而不是仅仅基于某个应用程序域。
       String类提供了两个静态函数来操作哈希表

    String.Intern 将字符串放入驻留池
    String.IsInterned 判断字符串是否处于驻留池中

  • GC无法回收驻留池内的对象。
  • 利用字面量值(直接用引号声明的string,例如"AAA")创建string对象会被存放在驻留池
  • 利用string.Intern()创建string对象会被存放在驻留池
  • 字面量值+字面量值拼接创建string对象会被存放在驻留池
  • 只有编译阶段的文本字符常量会被自动添加到驻留池,运行时动态创建的字符串不会进入驻留池。
3.1.具有相同字符序列的String对象不会重复创建

   0x0c5ce878和874是栈地址不同,但很明显两个字符串的堆内存地址是相同的:

3.2.字符串驻留机制对于string literal + string literal的运算生效

   如果A字符串和B字符串都在驻留池内,那么通过concat(+)连结A和B字符串时,结果会优先查询驻留池。

3.3.字符串Concat

   大部分函数也是封装在CPP内,不过能个大概意思应该是快速分配一个两段长度和的string内存,然后分别把两段string值填充进去,最后返回一段新的内存,这个函数可以证明两个非驻留池的字符串拼接时会生成一个新的字符串。

3.4.那么驻留池的Concat到底在哪处理的呢?

   在查看Concat的源代码的时候,并没有找到关于string的驻留池逻辑,而且上面的驻留池内的特性也说明了,只有编译时的文本才会缓存在驻留池内,所以就有了这段测试代码:


   果然,对于已经确定的文本,C#在编译的时候就自动帮我们把字符串拼接成一段完整的字符串了,所以对于驻留池内的文本拼接才会指向同一地址。
   而对于a和b,ab本身处于驻留池,而c使用a+b时调用了Concat函数,就触发了上面的拷贝到新内存流程,所以会产生新的字符串,并产生GC!

文章大纲