【主要内容】
关于C#的String。
C# String 和 string 有何区别
字符串的不变性
字符串比较 == 和 Equals(string str1, string str2)
字符串驻留
【概念阐述】
====================
@ C# String 和 string 有何区别
====================
1、string是c#中的关键字,String是.net Framework的类;
2、c# string映射为.net Framework的String,如果用string,编译器会把它编译成String,所以如果直接用String就可以让编译器少做一点点工作;
3、string始终代表System.String,String只有在前面有using System的时候并且当前命名空间中没有名为String的类型(class、struct、delegate、enum)
的时候才代表System.String
====================
@字符串的不变性
====================
1、一个字符串一旦被创建完成后,将不能再做任何改变,比如改变、增加或减少一个字符。
2、当把一个字符串变量赋给另一个字符串变量时,会得到内存中同一个字符串的两个引用,但与引用类型在常见的操作上又有区别。当你修改其中一个字符串的时候,
会创建一个全新的String对象,其引用的原字符串并不会改变,因而另一个字符串变量的值不变。
3、当我们创建了字符串对象a,它的值是“1234”,当我们再创建一个值为“1234”的字符串对象b时它不会再去分配一块内存空间,而是直接指向了a在内存中的地址。
=================================
@字符串比较 == 和 Equals(string str1, string str2)
=================================
==是Equals(string,string)的一个重载,两者内部机制完全一样。如果两字符串引用相同直接返回true,若不同则先比较引用再比较字符串的值。
return string .Equals(a, b);
}
public static bool Equals( string a, string b){
if (( object )a == ( object )b){ // 先比较引用是否相同,若相同直接返回True。
return true ;
}
if ((a != null ) && (b != null )) { // 引用不同的再比较值是否相同。
return a.Equals(b);
}
return false ;
}
【实验步骤】
=========================
@不可使用new操作符来创建字符串对象
=========================
public static void Main( string [] args){
string str = new string ( " a string " );
}
}
编译错误提示:
error CS1502: 与“string.String(char*)”最匹配的重载方法具有一些无效参数
error CS1503: 参数“1”: 无法从“string”转换为“char*”
事实上 string 类并未提供 string(string) 构造函数。
==================
@字符串连接测试(不变性)
==================
using System;
public class StringTest{
public static void Main( string [] args){
// String 为引用类型,但却有一些值类型的表现。
string s1 = " a string " ;
string s2 = s1; // s1 和 s2 都引用 "a string"。
Console.WriteLine( " s1 is " + s1);
Console.WriteLine( " s2 is " + s2);
s1 = " another string " ; // 修改 s1。
Console.WriteLine( " s1 now is " + s1);
Console.WriteLine( " s2 now is " + s2); // s2 的值仍为 "a string"。
Console.ReadLine();
}
}
输出:
s1 is a string
s2 is a string
s1 now is another string
s2 now is a string
=============
@ 字符串存储
=============
1、相同字符串在内存中只保存一份。
class Test{
static void Main( string [] args){
string a = " 1 " ;
string b = " 1 " ;
string c = " 2 " ;
}
}
}
使用visual studio 2005中的调试,转到反汇编,可以得到下面内容:
......
string a = "1";
0000002b mov eax,dword ptr ds:[022C3048h] // 与下面相同
00000031 mov ebx,eax
string b = "1";
00000033 mov eax,dword ptr ds:[022C3048h] // 与上面相同
00000039 mov esi,eax
string c = "2";
0000003b mov eax,dword ptr ds:[022C307Ch] // 不同
......
2、相同字符串GetHashCode()返回相同值。
public class Test{
public static void Main( string [] args){
string a = " 0 " ;
string b = " 0 " ;
string c = " 1 " ;
Console.WriteLine(a.GetHashCode());
Console.WriteLine(b.GetHashCode());
Console.WriteLine(c.GetHashCode());
}
}
输出:
-842352752
-842352752
-842352753
3、字符串驻留
public class Test{
public static void Main( string [] args){
String s = “Hello”;
Console.WriteLine(Object.ReferenceEquals(“Hello“,s));
}
}
输出:
True
说明:当 CLR 初始化时,它会创建一个内部的散列表,其中键位字符串,值为指向托管堆中字符串对象的引用,初始化为空。当JIT编译器编译方法时,
它会在散列表中查找每一个常量字符串,并添加到散列表中,值相同的字符串不重复添加。对于上面的代码,编译器会对查找到的第一个"Hello"字符串,
将现在托管堆中构造一个新的 String 对象(指向在字符串),然后将 "Hello" 字符串和指向该对象的引用添加到散列表中。由于散列表中已经存在 "Hello"
字符串,对于查找到的第二个 "Hello" 字符串,将不执行任何操作。
代码执行时,它会在第一行发现一个 "Hello" 字符串引用,于是便在内部散列表中查找 "Hello",找到后将先前创建的 String 对象的引用保存到变量 s 中。
当执行第二行代码时,CLR 会再一次在内部散列表中查找 "Hello",并把对应的 String 对象的引用传递给 Object 的静态方法 ReferenceEquals 作为参数。
通过上面的分析,显然结果为 True。当一个引用字符串的方法被 JIT 编译时,所有嵌入在源代码中的常量字符串总会被添加到 CLR 内部的散列表中,
但是,运行时动态创建的字符串却不会,参看下面的代码:
public class Test{
public static void Main( string [] args){
string s1 = " Hello " ;
string s2 = " Hel " ;
string s3 = s2 + " lo " ;
Console.WriteLine(Object.ReferenceEquals(s1,s3));
}
}
输出:
False
说明:在上面的代码中,s2 引用的字符串 "Hel" 和一个文本常量字符串 "lo" 连接构造一个新的位于托管堆中的字符串对象,该引用保存在 s3 中,但并未添加到散列表中,
与散列表中的 "Hello" 在托管堆中各有一份存储,因此返回的结果是 False。需要注意的是,如果 s3 连接的是两个字符串常量 "Hel" + "lo",则又将返回 True。这是因为
C# 编译器在将代码编译成 IL 指令时会将两者连接。
============
@字符串比较
============
public class Test{
public static void Main( string [] args){
string s1 = " Hello " ;
string s2 = " Hel " ;
string s3 = s2 + " lo " ;
Console.WriteLine(Object.ReferenceEquals(s1,s3));
Console.WriteLine(Equals(s1,s3));
Console.WriteLine(s1 == s3);
}
}
输出:
True
True
说明:参看概念阐述中对字符串比较内部实现的说明。虽然 s1 和 s3 引用不同,但String 的 Equals(string,string) 方法还会对字符串的值进行比较,因为值相同,所以返回结果为 True。显然用 Equals(string,string)/== 效率上要比仅比较引用的 ReferenceEquals(string,string) 差很多,如果应用程序中所有的字符串比较都仅比较引用,性能将会大大提升,String 类提供的两个静态方法允许我们做到这一点。
public static void Main( string [] args){
string s1 = " Hello " ;
string s2 = " Hel " ;
string s3 = s2 + " lo " ;
s3 = String.Intern(s3);
Console.WriteLine(Object.ReferenceEquals(s1,s3));
Console.WriteLine(Equals(s1,s3));
Console.WriteLine(s1 == s3);
}
}
输出:
True
True
True
说明:方法 Inter(string) 在 CLR 内部散列表中查找参数指定的字符串,如果能找到返回其引用,如果未找到,字符串将被添加到散列表中,并返回引其引用。
如果 String 对象不再被进程中的所有应用程序域所引用(作为参数传递给 Intern 方法的那个 String 对象),垃圾收集器可以收回其所占内存及在三列表中的记录。
一个字符串对象可以被同一个进程中的多个应用程序域访问,即字符串的驻留是以进程为单位的。参看下面的代码:
public class CreateString{
public static void Main( string [] args){
string s1 = " Hello " ;
string s2 = s1;
string s3 = s1 + " joe " ;
// MyIntern();
Console.WriteLine(String.IsInterned(s1) != null );
Console.WriteLine(String.IsInterned(s2) != null );
Console.WriteLine(String.IsInterned(s3) != null );
}
public static void MyIntern(){
String.Intern( " Hello joe " );
}
}
输出:
True
True
False
如果将注释部分加入到代码中则输出:
True
True
True
【参考资源】
《你真的了解.net中的String吗》 http://terrylee.cnblogs.com/archive/2005/12/26/304876.aspx
《进一步了解 String》 http://lixianhuei.cnblogs.com/archive/2005/12/27/305445.html
《Microsoft.NET框架程序设计》