这篇文章我来总结一些string相关知识。
System.String类型:平时在编程中对于string的用法应该是特别频繁的。通常我们会把string,int ,float放在一起比较,由于后面两个都是值类型,所以非常想当然的会认为string也会是值类型,这是错误的,string由于自身特殊的原因,它最终属于引用类型,当然它的最终基类还是Object。字符串存放在托管堆上,并不存放在堆栈中。
构造字符串:如何来构造一个字符串呢?
第一:既然string属于引用类型,那么是否可以用new操作符来生成呢?下面的代码是错误的。
第二:我们通常的做法是不用new,而是直接赋值。
生成的部分IL代码:在生成的IL代码中并没有出现newobj操作符,但出现了ldstr:推送对元数据中存储的字符串的新对象引用。
IL_0006: stloc.0
字符串驻留池(String Pooling):公共语言运行库通过维护一个哈希表来存放字符串,该表称为字符串驻留池。它包含程序中以编程方式声明或创建的每个唯一的字符串的一个引用。因此,具有特定值的字符串的实例在系统中只有一个。 [应该是应用了单例模式的应用。单例可以参考:老生常谈:单件模式],这里非常谢谢飞林沙的提示,应该是享元模式,同样可以参考如下文章:老生常谈:享元模式
字符串驻留:根据字符串驻留池的定义可以看出字符串驻留技术的用处。主要目的是减少内存开销和系统开销,充分利用资源。提供了两个重要的方法:
1:String.Intern :检索系统对指定 String 的引用。如果 str 的值已经留用,则返回系统的引用;否则返回对带有 str 值的字符串的新引用。
2:String.IsInterned 方法 :检索对指定 String 的引用。如果 str 位于公共语言运行库“拘留池”中,则为对它的 String 引用;否则为 nullNothingnullptrnull 引用
字符串的不可更改特性:当一个字符串创建后,我们就不能对它进行增加长,或者是缩减,也不能修改其中的任何字符。但有朋友可以会问,那为什么能对字符串进行那么多的操作呢?例如:"+"操作符,Substring等等。其实这些操作都会产生一个全新的字符串,而不是在原来字符串在修改。只要是把操作后的数据赋值给一个新的字符串变量,那么就是产生一个和原字符串完全不同的对象。先看下赋值的情况:把一个变量直接赋值给一个新的字符串变量,根据上面的理论来看,下面应该会返回false,但实际结果返回的是true,因为在字符串驻留的存在。
string c = b;
Console.WriteLine(Object.ReferenceEquals(b, c));
但是这并不代表b和c是一样的,我们再看下如下代码:分别分返回aaa,aaaa,false,既然string属于引用类型,但为什么修改c的值后,b的值没有跟着变呢,这只因为把字符串赋值给另一个字符串时,会创建一个全新的String对象。
string c = b;
c = b + "a";
Console.WriteLine(b);
Console.WriteLine(c);
Console.WriteLine(Object.ReferenceEquals(b, c));
字符串中的"+"操作符:通常我们会对字符串进行修改,例如"+"号,表示连接多个字符串。但加号会产生非常多的字符串,这样会造成非常多的内存开销和系统开销,这里可以利用StringBuilder来解决多字符串连接的性能问题。来看下下面的示例代码:
b = b + "a";
StringBuilder strb = new StringBuilder();
strb.Append("aaa");
strb.Append("a");
IL代码:由于ConCat操作后会生成全新的对象,而StringBuild是在原对象上修改变量,并不会生成新对象,这样就会产生性能优势。上面的代码只是示范,一般在大量字符串拼接时这种性能问题会倍增。
{
.entrypoint
// 代码大小 50 (0x32)
.maxstack 2
.locals init ([0] string b,
[1] class [mscorlib]System.Text.StringBuilder strb)
IL_0000: nop
IL_0001: ldstr "aaa"
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldstr "a"
IL_000d: call string [mscorlib]System.String::Concat(string,
string)
IL_0012: stloc.0
IL_0013: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
IL_0018: stloc.1
IL_0019: ldloc.1
IL_001a: ldstr "aaa"
IL_001f: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0024: pop
IL_0025: ldloc.1
IL_0026: ldstr "a"
IL_002b: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0030: pop
IL_0031: ret
} // end of method Program::Main
安全字符串:如果字符串中存放了此比较敏感的信息,例如用户的密码,信用卡信息之类的,如果此时有非安全代码或者是非托管代码访问这些字符串,这些代码可以扫描进程的地址空间,访问包含敏感数据的字符串,以一种非授权的方式来使用这些数据。即使只使用这些字符串时间很短,马上回进行垃圾回收,也是需要一定时间的,而且string本身无法修改的特性,使这些非安全的或者是非托管代码在访问时会留string的副本在内存中。
FCL解决了这个问题,它就是System.Security.SecureString:表示应保密的文本。文本在使用时出于保密目的被加密,并在不再需要时从计算机内存中删除。无法继承此类。当你构造一个SecureString对象时,系统会在内存中分配一个非托管区域,里面包含一个字符数组,里面的内容均是经常加密的。之所以是非托管空间,就是为了不让垃圾回收器来管理。
System.Security.SecureString实现了IDisposable接口,这样可以保证在使用完后立即对字符串进行销毁。