C#-String的恒定性和驻留池

脑图概览#

image

字符串恒定性#

字符串一旦创建,就会在托管堆上分配一块连续的内存空间,对其任何改变都不会影响到原String对象,而是重新创建新的String对象。

为什么设计string具有恒定性呢#

《你必须知道的.NET》解释是:String类型是不变模式在.NET 中的典型应用。

带来的好处#

  • 保证对String对象的任意操作不会改变原字符串;
  • 意味着操作字符串不会出现线程同步;
  • 恒定性一定意义上成就了字符串驻留;

对象恒定意味着String类型必须为密封类

字符串驻留池#

字符串因为其恒定的特点,对字符串的操作都会创建新字符串,性能和内存会有很大消耗。鉴于string类型的频繁使用,CLR针对其采用字符串驻留机制进行优化。

总得原理是:对于相同的字符串,CLR不会为其分别分配内存,而是共享同一内存。

实现:CLR内部有一个哈希表(Hash Table) 管理创建的大部分string对象。key就是string本身,value就是string的内存地址。

字符串驻留池实现原理#

CLR维护一个类似于哈希表的内部结构,用于维护对于字符串的统一管理,但JIT编译时,CLR首先查找哈希表,如果没有找到匹配的字符串记录,则在托管堆中创建新的string实例,并为哈希表添加一个键值对记录;下一次查找相同string时,则只返回该记录的值给第二次创建的string对象。

通过这种方式,字符串驻留机制有效实现了对string的池管理,节省了大量的内存空间。

注意:哈希表中不维护动态生成的字符串。

IsInterned()和Intern()#

  • IsInterned():str位于驻留池时,则IsInterned(str)返回str的引用;否则返回null引用。

  • Intern():str位于驻留池时,则IsInterned(str)返回str的引用;否则将该str字符串添加的哈希表中,并返回引用 。

相同点:都是在哈希表中查找是否存在str参数字符串,找的到就返回已存在string对象的引用。区别在于找不到的话Intern会向哈希表添加字符串,IsInterned不会。

动态字符串维护在拘留中吗?#

例1:

void Main()
{
	string strA = "abcdef";
	string strC = "abc";
	string strD = strC + "def";
	
	Console.WriteLine(ReferenceEquals(strA, strD));//False

	string strE = "abc"+"def";
	Console.WriteLine(ReferenceEquals(strA, strE));//True
}

strA和strD的值相同,但strD是动态添加的不在驻留池中为维护,所以是两个内存地址。

strE是字符串常量相加,在驻留池中维护,所以引用同一内存地址。

结论:动态生成字符不在驻留池中维护

IsInterned返回非null一定在拘留池中吗?#

通过以下代码发现答案是否定的。

s1和s1返回的引用虽然相同但是,s1是维护在哈希表中,s2是动态字符串不维护在哈希表中

string s1 = "abc";
string s2 = "ab";
s2 += "c";

string s3 = "ab";

Console.WriteLine(string.IsInterned(s1) ?? "null");//abc
Console.WriteLine(string.IsInterned(s2) ?? "null");//abc

Console.WriteLine(ReferenceEquals(s1,s3));//False

检测你是不是理解了驻留池#

场景一:#

s1 在哈希表中可以找到

string s1 = "abc";
Console.WriteLine(string.IsInterned(s1) ?? "null");//abc

场景二:#

动态字符串不在哈希表中维护

string s1 = "ab";
s1 += "c";
Console.WriteLine(string.IsInterned(s1) ?? "null");//null

场景三:#

s1,s3 都在哈希表中维护;s2不维护在哈希表中,但是在哈希表可以找到key为‘abc’的结果,也就是指向s1的内存地址;s1和s2的内存地址是不一样的

string s1 = "abc";
string s2 = "ab";
s2 += "c";

string s3 = "ab";

Console.WriteLine(string.IsInterned(s1) ?? "null");//abc
Console.WriteLine(string.IsInterned(s2) ?? "null");//abc
Console.WriteLine(string.IsInterned(s3) ?? "null");//ab

Console.WriteLine(ReferenceEquals(s1,s2));//False

场景四:#

s3 不维护在哈希表中,返回值s1地址

string s1 = "abc";
string s2 = "ab";
string s3 = s2 + "c";

Console.WriteLine(string.IsInterned(s3) ?? "null");//abc

场景五:#

驻留池在CLR加载时创建的,所以s1在s2定义前就被添加在哈希表中。所以
s2虽然不维护在哈希表中但返回的是s1的地址。

string s2 = "ab";
s2 += "c";

Console.WriteLine(string.IsInterned(s2) ?? "null");//abc
string s1 = "abc";

场景六:#

GetStr()在s2定义后调用

void Main()
{
	string s2 = "ab";
	s2 += "c";
	Console.WriteLine(string.IsInterned(s2) ?? "null");//null
	string s1 = GetStr();
}
private static string GetStr()
{
	return "abc";
}

GetStr()在s2定义前调用

void Main()
{
	string s1 = GetStr();
	string s2 = "ab";
	s2 += "c";
	Console.WriteLine(string.IsInterned(s2) ?? "null");//null
}
private static string GetStr()
{
	return "abc";
}

只因为GetStr()位置不同,结果就不同,这又是如何解释呢?

场景七:#

全局静态字符串变量和局部字符串变量一样

public static string s1="abc";
void Main()
{
	string s2 = "ab";
	s2 += "c";
	Console.WriteLine(string.IsInterned(s2) ?? "null");//abc
}

字符串驻留(上)---带着问题思考

字符串驻留跨域#

字符串驻留是进程级别,可以跨应用程序域(AppDomain)而存在。因为驻留池是在CLR加载时创建的,分配在System Domain中,被进程中的所有AppDomain所共享,生命周期不受GC控制。只能在进程结束后哈希表中引用的字符串对象才会被释放。

string和System.String#

  • string 是c#基元类型,System.String是框架类库(FCL)的基本类型;string和System.String有直接映射关系。
  • 在IL层面两者没有不同。

string 和StringBuilder#

string 对象恒定不变,StringBuilder对象表示的字符串是可变的。后者是.NET 提供的动态创建String对象的高效方式,解决了String对象恒定性带来的影响,比如在对string对象多次修改会创建大量string对象的问题。

StringBuilder 使用场景:大量无法预知次数的字符串操作

作者:ricolee

出处:https://www.cnblogs.com/ricolee/p/cs-string.html

版权:本作品采用「C」许可协议进行许可。

posted @   牧白  阅读(601)  评论(0编辑  收藏  举报
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示