首页 > 代码库 > C#文本处理(String)学习笔记
C#文本处理(String)学习笔记
摘要:string是编程中使用最频繁的类型。一个string表示一个恒定不变的字符序列集合。string类型直接继承自object,故他是一个引用类型,也就是说线程的堆栈上不会有任何字符串(直接继承自object的类型一定是引用类型,因为所有的值类型都继承自System.ValueType。值得指出的是System.ValueType是引用类型)。
string是编程中使用最频繁的类型。一个string表示一个恒定不变的字符序列集合。string类型直接继承自object,故他是一个引用类型,也就是说线程的堆栈上不会有任何字符串(直接继承自object的类型一定是引用类型,因为所有的值类型都继承自System.ValueType。值得指出的是System.ValueType是引用类型)。
一、创建字符串
C#将string认为是基元类型,也就是说编译器也许在源代码中用文本常量来直接表达字符串。编译器会将这些文本常量字符串存放在托管模块的元数据中。在C#中创建string。
不能通过new操作符来创建string
{
static void Main(string[] args)
{
string hap = new string("heaiping");
}
}
以上代码会出现编译错误。
相反,应该使用下面的简化语法
{
static void Main(string[] args)
{
string hap ="heaiping";
}
}
为什么呢,请看下面代码。
{
class Program
{
static void Main(string[] args)
{
string s = "heaiping";//不使用new
SomeClass sc = new SomeClass();//使用new
}
}
class SomeClass
{
}
}
编译以上代码查看ILDasm生成的IL代码如下。
{
.entrypoint
// 代码大小 14 (0xe)
.maxstack 1
.locals init ([0] string s,
[1] class StringStudy.SomeClass sc)
IL_0000: nop
IL_0001: ldstr "heaiping"//此处创建string
IL_0006: stloc.0
IL_0007: newobj instance void StringStudy.SomeClass::.ctor()//此处创建SomeClass
IL_000c: stloc.1
IL_000d: ret
} // end of method Program::Main
如上所见C#对于对象实例的IL指令为newobj,然后调用其构造函数,而对于string,它用了一个特殊的指令ldstr(是不是loadstring,呵呵),该指令通过从元数据中获取文本常量来构造string对象,这表明,CLR有一个更为高效特殊的字符串构造方式。
string类型也通过了通过new来构造而不是通过文本元数据来构造的构造器,可以接受char*等参数,书上说是为了托管C++提供的,这个就不研究了~~~~~~~
看下面的代码
{
static void Main(string[] args)
{
string s = "heaiping";
s = s + " " + "soft";
}
}
上面的代码使用了+操作符来连接字符串,书上建议不要使用+,因为它会在要执行垃圾回收器的托管堆上创建多个字符串,不是单纯的附在后面。应该使用System.Text.StringBuilder(下次学习);
还有一种特殊的声明方式,允许编译器将引号之间的字符都认为是字符串的一部分。
{
static void Main(string[] args)
{
//fileA和fileB是一样的
string fileA = "C://Windows//Temp";
string fileB = @"C:/Windows/Temp";
}
}
@符号会爆速编译器该字符串为一个字面字符串,告知编译器将反斜杠看为字符而不是转义字符。
二、字符串的恒定性
字符串的恒定性,就是说,一个字符串一旦被创建,就不可能将其变长、变短、或者改变其中任何的字符。
三、字符串比较
罗列一下
成员 | 成员类型 | 描述 |
Compare | 静态方法 | 按照字符串的排序比较,可以控制语言文化,以及是否考虑大小写 |
CompareTo | 实例方法 | 按照字符串的排序比较,内部使用了当前线程的语言文化 |
StartsWith/EndsWith | 实例方法 | 是否以指定的字符串开头或者结尾,大小写敏感,内部使用了当前线程的语言文化 |
CompareOrdinal | 静态方法 | 按照字符集比较,不考虑语言文化,大小写敏感,比较快 |
Eauals | 静态/实例 | 静态方法比较字符集,实例方法内部调用CompareOrdinal,静态方法首先会检查两个引用是否指向同一个对象,如果是则不再比较字符集 |
GetHashCode | 实例方法 | 返回列散码 |
除以上,string类型还承载了==和!=操作符,其方法内部都是调用string的静态Equals方法实现的
对于以上比较有以下说明:
- 如果是判断字符串是否相等,用CompareOridinal,因为它仅仅是比较字符集是否相等,比较快。
- 如果是逻辑比较,呈现给用户,何为逻辑比较,虽然有些字符串的字符集不一样,但是逻辑上确实是等的,应该使用Compare方法,Compare内部使用了特定语言文化的排序表。
在字符串的比较时,语言文化对其影响是很大,Compare方法在内部首先获取与调用线程相关的CurrentCulture,然后读取CurrentCulture的CompareInfo属性。因为CompareInfo对象封装了一个和语言文化相关联的字符比较表,所以每一种文化仅有一个CompareInfo对象。
四、字符串驻留
字符床的比较是很浪费性能的,CLR通过字符串驻留(string interning)机制来提高性能,看下面代码:
namespace StringStudy
{
class Program
{
static void Main(string[] args)
{
string s = "hap";
Console.WriteLine(object.ReferenceEquals("hap",s));
SomeClass sc = new SomeClass();
Console.WriteLine(object.ReferenceEquals(new SomeClass(), sc));
}
}
class SomeClass
{
}
}
按照引用类型的特性,上面的两个输出都应该为False,因为都是不同的引用在比较,可是 Console.WriteLine(object.ReferenceEquals("hap",s));却输出为True,为什么,难道string不是引用类型吗?? string当然是引用类型,上面的表现由CLR对于string的特殊处理决定的:
当CLR初始化的时候,它会创建一个内部的散列表,键为字符串,值为指向托管堆中字符串对象的引用。刚开始,该表为空。当JIT编译器编译方法时,它会在散列表中查找每一个文本常量字符串。对于上面的关于string的代码,编译器会首先查找“hap”字符串,并且因为没有找到(第一次还没有),它便就在托管堆上面构造一个新的string对象(指向hap),然后将hap字符串和指向该对象的引用添加到散列表(此时散列表中已经存在键为hap的值),接着JIT编译器在编译器中查找第二个hap字符串,当然此时散列表中已经存在hap,所以不会执行任何操作,代码开始执行。
当代码执行时,它会在第一行发现需要hap字符串的引用。于是,CLR便在其内部的散列表中查找hap,并且会找到它,这样指向先前创建的string对象的引用就被保存在变量s中。当再往下执行,CLR会再一次在其内部散列表中查找hap,并且仍然会找到,这样子,指向同一个string对象的引用会被传递给object的ReferenceEquals作为第一个参数,当然和第二个参数是同一个引用,自然比较结果为True。
继续看下面代码:
{
class Program
{
static void Main(string[] args)
{
string s1 = "hap";
string s2 = "h";
string s3 = s2 + "ap";
string s4 = "h" + "ap";
Console.WriteLine(object.ReferenceEquals(s1,s3));//false
Console.WriteLine(s1.Equals(s3));//true
Console.WriteLine(object.ReferenceEquals(s1, s4));//true
}
}
}
这次的输出搞的怪怪的,心里有点毛,到底是怎么回事?原来当一个引用字符串的方法被JIT编译时,所有嵌入在源代码中的文本常量总会被添加到散列表中,s2引用的字符串(h)和一个文本常量字符串(ap)被连接在一起。结果是一个新构造的、位于托管堆中有s3引用的字符串对象,这个动态创建的字符串包含的是一个hap,但是它没有被添加到CLR散列表中,而是另起炉灶,不同的引用所有ReferenceEquals返回fasle,调用Equals相等是因为他们有相同的字符集。
至于s4=”h"+"ap",是因为IL指令会将两个文本常量字符串连接为一个文本常量字符串hap,所有输出为True。
ReferenceEquals方法在比较时不需要逐个字符的去比较,只比较引用,效率明显要高于Equals,如果将程序中所有的字符串比较都用引用而不是字符集,那么系统的性能将得到很大的提高。如果有一个方法要是能将含有相同字符集的动态字符串变为托管堆中的一个字符串对象的话,应用程序需要的对象也将更少,从而可以提升系统性能。非常幸运。string类型提高了两个静态方法将可以做到这一点。
public static string IsInterned(string str);
第一个方法Intern接受一个string参数,然后在CLR内部散列表中查找它。如果能够找到该字符串,Intern将返回已经存在的引用。如果找不到,该字符串将被添加到散列表中,Intern最后也会返回它的引用。如果引用程序不再保存作为参数传递的string的引用,垃圾回收器将会回收它。对上面的程序用Intern重新修改:
{
class Program
{
static void Main(string[] args)
{
string s1 = "hap";
string s2 = "h";
string s3 = s2 + "ap";
s3 = string.Intern(s3);
Console.WriteLine(object.ReferenceEquals(s1,s3));//true
Console.WriteLine(s1.Equals(s3));//true
}
}
}
输出全部为true,神奇啊。因为Intern也是需要执行时间的,所以书上建议只有需要多次比较同一个字符串时,才 应该使用字符串驻留技术,否则得不偿失。
需要注意的是,垃圾回收器不会释放CLR内部散列表中引用的字符串对象。只有当应用程序域都不再应用这些字符串对象时,他们才会被释放。
IsInterned方法与Intern方法的不同之处是如果在散列表中找不到将会返回null,而不是创建。
{
class Program
{
static void Main(string[] args)
{
string s1 = "heaiping";
string s2 = "h";
string s3 = s2 + "ap";
s3 = string.IsInterned(s3);
if (s3 == null)
{
Console.WriteLine(0);
}
else
{
Console.WriteLine(1);
}
}
}
}
上面程序输出0,因为就当前程序来说散列表中只存在heaiping而不存在hap,所以最后s3变为null。
string还有一些成员Length,IndexOF...Copy...等等。。。不再说了,累了啊。
对了以前发过一个关于string的小组讨论http://home.cnblogs.com/group/topic/38270.html
原文如下:
class Class1
{
static void StrChange(string str)
{
str = "hellow";
}
static void Main()
{
string str = "123";//申明一个字符串
StrChange(str);//调用方法
Console.WriteLine(str);//输出字符串
}
}
输出的结果是 "123"
string 到底是值类型还是引用类型?
如果是值类型,结果倒还说的过去.但是我记得string 是引用类型啊...难道是我记错了??
如果是引用类型的话.输出的结果应该是: "hellow"
请问这是为什么啊?? 大家帮忙解释一下..谢谢
现在明白了,其实就是与CLR对于string的这个散列表有关,
至于string是特殊的引用类型,个人感觉特殊就特殊在这个散列表上吧
C#文本处理(String)学习笔记