C#字符串

【主要内容】

关于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,若不同则先比较引用再比较字符串的值。

public static bool operator ==(string a, string b){
      
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 class CreateString{
 
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、相同字符串在内存中只保存一份。

using System;
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()返回相同值。

using System;
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、字符串驻留

using System;
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 内部的散列表中,
但是,运行时动态创建的字符串却不会,参看下面的代码:

using System;
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 指令时会将两者连接。

============
@字符串比较
============

using System;
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 class Test{
 
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 对象),垃圾收集器可以收回其所占内存及在三列表中的记录。
一个字符串对象可以被同一个进程中的多个应用程序域访问,即字符串的驻留是以进程为单位的。参看下面的代码:

using System;
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框架程序设计》

 

posted @ 2008-06-27 08:08  NanKe Sir's Blog  阅读(1755)  评论(2编辑  收藏  举报