无论在哪种语言与平台中,字符串都是我们最常使用的对象。
.Net与C#语言把字符串在表面上做的相当易用,但是只有在深入理解字符串在CLR的驻留形式后才能更加合理高效的使用字符串对象。
1 字符串的驻留形式
先看一个sample
static void Main(string[] args)
{
//.Net Framework中将一个应用程序的string都存放在一个Hashtable中
//key就是值 value是堆地址
string str1 = "hello"; //申请一块堆内存 把地址放在StringHashtable的key为hello的元素中
string str2 = "hello";//由于上一句已经创建了key为hello的元素 所以不使用newobj 申请新的堆内存
string str3 = "h" + "e" + "l" + "l" + "o"; //编译成MSIL语言时 已经与string str3 = "hello"一样了
string str4 = new string(new char[] { 'h', 'e', 'l', 'l', 'o' }); //自己显示进行new
string str5 = "hello2"; //申请一块堆内存 key为hello2
Console.WriteLine(object.ReferenceEquals(str1, str2).ToString()); //True 引用同一块堆内存
Console.WriteLine(object.ReferenceEquals(str1, str3).ToString()); //True 也是引用同一块堆内存
Console.WriteLine(object.ReferenceEquals(str1, str4).ToString()); //False 引用了不同的堆内存
str2 = "hello2"; // 每次对字符串对象赋值时 都先从StringHashcode中检索是否有重复的key 检索到了str5
Console.WriteLine(object.ReferenceEquals(str1, str2).ToString()); //False str2与str1已经不引用同一个堆
Console.WriteLine(object.ReferenceEquals(str2, str5).ToString()); //True 变成与str5引用同一个堆内存
str1 = Console.ReadLine();
str2 = Console.ReadLine();
Console.WriteLine(object.ReferenceEquals(str1, str2).ToString());
Console.WriteLine(object.ReferenceEquals(str1, string.Empty).ToString());
Console.ReadLine();
}
{
//.Net Framework中将一个应用程序的string都存放在一个Hashtable中
//key就是值 value是堆地址
string str1 = "hello"; //申请一块堆内存 把地址放在StringHashtable的key为hello的元素中
string str2 = "hello";//由于上一句已经创建了key为hello的元素 所以不使用newobj 申请新的堆内存
string str3 = "h" + "e" + "l" + "l" + "o"; //编译成MSIL语言时 已经与string str3 = "hello"一样了
string str4 = new string(new char[] { 'h', 'e', 'l', 'l', 'o' }); //自己显示进行new
string str5 = "hello2"; //申请一块堆内存 key为hello2
Console.WriteLine(object.ReferenceEquals(str1, str2).ToString()); //True 引用同一块堆内存
Console.WriteLine(object.ReferenceEquals(str1, str3).ToString()); //True 也是引用同一块堆内存
Console.WriteLine(object.ReferenceEquals(str1, str4).ToString()); //False 引用了不同的堆内存
str2 = "hello2"; // 每次对字符串对象赋值时 都先从StringHashcode中检索是否有重复的key 检索到了str5
Console.WriteLine(object.ReferenceEquals(str1, str2).ToString()); //False str2与str1已经不引用同一个堆
Console.WriteLine(object.ReferenceEquals(str2, str5).ToString()); //True 变成与str5引用同一个堆内存
str1 = Console.ReadLine();
str2 = Console.ReadLine();
Console.WriteLine(object.ReferenceEquals(str1, str2).ToString());
Console.WriteLine(object.ReferenceEquals(str1, string.Empty).ToString());
Console.ReadLine();
}
开始时候的str1,str2,str3其实在翻译为MSIL后,都是相同的string str = "hello";
然后该应用程序会有一个字符串池,应该是一个hashtable,保存着所有的字符串,后面的str2="hello2",使str2与str5指向了同一块内存。
最后我又试验了一下字符串池的机制是否在运行时也是启作用的,发现字符串池仅仅是编译时,优化了应用程序的大小,应用程序启动时,优化了内存占用大小,而运行时没有这个机制,每次对一个string的赋值都会导致在堆内存分配一块新的空间。