.NET基础 (08)字符串处理
字符串处理
1 System.String是值类型还是引用类型
2 StringBuilder类型有何作用
3 如何在String和Byte[]对象之间进行转换
4 简述BASE64编码的作用以及C#中对其的支持
5 SecureString的实例如何被分配和释放
6 什么是字符串池机制
字符串处理
1 System.String是值类型还是引用类型
System.String 是引用类型,它的对象在初始化后就不能再做修改,任何试图修改字符串对象的动作都将导致一个新的字符串对象的产生。
示例代码:
static void Main(string[] args) { String a = "我是字符串"; String b = a; Console.WriteLine(Object.ReferenceEquals(a, b)); //尝试使用引用b修改a指向的对象 b = "我是新的字符串"; Console.WriteLine(a); Console.WriteLine(b); Console.WriteLine(Object.ReferenceEquals(a, b)); Console.Read(); }
输出结果:
True
我是字符串
我是新的字符串
False
String的性能问题产生于其内容不可修改的特性。当一个字符串对象被修改、插入、连接、截断是,新的字符串对象将被分配,这会影响性能。更加常见的情况是,一个字符串对象的最终生成需要经过一个组装过程,而在这个组装过程中必将产生很多临时字符串对象。这些对象在堆上分配,需要垃圾回收器来回收,这些动作会对程序产生巨大的影响。而事实上,在字符串组装过程中,其临时对象都不是最终需要的,可以说是不必要分配的。
这样就产生了对StringBuilder类型的需求。StringBuilder类型的设计思想源于构造器的设计模式。构造器设计模式致力于解决复杂对象的构造问题,而对于字符串对象,正需要这样的构造器来进行组装。StringBuilder类型在最终产生String对象之前,将不产生任何String对象,这很好的解决了字符串的性能问题。一旦StringBuilder的ToSting方法被调用后,最终的字符串就被产生,而随后的操作将导致一个新的字符串对象的分配。
对于非托管的代码,字符串的操作是直接基于内存的,如果使用字符串类型和非托管资源进行操作,那潜在地违反了字符串对象不可变的原则。所有StringBuilder常常别用来和非托管代码进行交互。
比特
比特(bit)是指一个位。
字节
字节(byte)在C#中由8个比特来构成。它的值可以由一个0~255的整数表示,但是C#中不允许隐式地把一个整数变量赋值给一个字节变量,下面的代码将编译错误:
int i = 10;
byte b = i;
编码
编码可以说是数字信息和现实信息的转换机制。一种编码常常就定义一种字符集合转换的原则,常用的编码方式包括UTF8、GB2312、Unicode 等。
字符串和字节数组的转换依赖于编码方式的使用,不同的编码准则使用不同的算法进行。System.Text.Encoding类型提供了大部分常见的编码算法支持,用以进行字符串和字节数组直接的转换。
示例代码:
class StringByte { static void Main(string[] args) { String s = "我是字符串,I am string"; //字节数组转换到字符串 Byte[] utf8 = StringToByte(s, Encoding.UTF8); Byte[] gb2312 = StringToByte(s, Encoding.GetEncoding("GB2312")); Byte[] unicode = StringToByte(s, Encoding.Unicode); Console.WriteLine(utf8.Length); Console.WriteLine(gb2312.Length); Console.WriteLine(unicode.Length); //转换回字符串 Console.WriteLine(ByteToString(utf8, Encoding.UTF8)); Console.WriteLine(ByteToString(gb2312, Encoding.GetEncoding("GB2312"))); Console.WriteLine(ByteToString(unicode, Encoding.Unicode)); Console.Read(); } static Byte[] StringToByte(String s, Encoding encoding) { return encoding.GetBytes(s); } static String ByteToString(Byte[] b, Encoding encoding) { return encoding.GetString(b); } }
输出:
27
22
34
我是字符串,I am string
我是字符串,I am string
我是字符串,I am string
BASE64编码是一种用于混淆明码的编程方式,其算法是把原来8位字节数组顺序分配到新的6位字节数组中,再在每个字节的高2位填充0来组成新的8位字节数组。在.NET中Covert类型可以用来进行BASE64字符串和8位字节数组之间的转换。
static void Main(string[] args) { String abc = "abcde"; //生成UTF8字节数组 Byte[] bytes = Encoding.UTF8.GetBytes(abc); //转换成Base64字符串 String base64 = BytesToBase64(bytes); Console.WriteLine(base64); //转换回UTF8字节数组 bytes = Base64ToBytes(base64); //转换回字符串 Console.WriteLine(Encoding.UTF8.GetString(bytes)); Console.Read(); } //把8位字节数组转换成Base64字符串 static String BytesToBase64(Byte[] bytes) { try { return Convert.ToBase64String(bytes); } catch { return null; } } //把Base64字符串转换成8位字节数组 static Byte[] Base64ToBytes(String base64) { try { return Convert.FromBase64String(base64); } catch { return null; } }
输出:
YWJjZGU=
abcde
System.SecureString 提供了加密的字符串类型。其对象会被分配在非托管堆中,并以加密的形式保存。对于SecureString的操作都是逐字符的,SecureString会负责在操作时进行加密和解密。SecureString实现了标准的Dispose/Finalize方法,对象被释放是先被全部布置为0,以保证机密信息不会在内存中驻留过长时间。
static void Main(string[] args) { //使用using保证Dispose方法被调用 using (SecureString ss = new SecureString()) { //只能逐字符地访问SecureString对象 ss.AppendChar('a'); ss.AppendChar('c'); ss.AppendChar('d'); ss.InsertAt(1, 'c'); PrintSecurityString(ss); Console.Read(); } } public unsafe static void PrintSecurityString(SecureString ss) { char* buffer = null; try { //只能逐字符地访问SecureString对象 buffer = (char*)Marshal.SecureStringToCoTaskMemUnicode(ss); for (int i = 0; *(buffer + i) != '\0'; i++) Console.Write(*(buffer + i)); Console.Write("\r\n"); } finally { //释放内存 if (buffer != null) Marshal.ZeroFreeCoTaskMemUnicode((System.IntPtr)buffer); } }
为了显示SecureString的内容,程序要访问非托管资源内存块,PrintSecurityString使用了unsafe关键字,所以编译时需要添加/unsafe 开发。
如果出现编译错误:不安全代码只会在使用 /unsafe 编译的情况下出现。只需要把程序属性中的“生成”标签下的“允许不安全代码”勾上就好了。
运行输出:
accd
字符串池机制致力于改善程序的性能。CLR会保留程序中出现过的字符串对象的集合,并且在需要新的字符串时,先检查已有的集合,在检查成功时返回已有对象的引用。如果不存在,则新分配一个字符串对象,同时把其添加到内部容器中。但是当程序用new关键字明显地申明新分配一个字符串对象时,该机制不会起作用。字符串池机制可以通过程序集元数据特性进行控制,C#默认是打开字符串池机制的。
static void Main(string[] args) { //两个字符串对象,理论上引用应该不相等 //但由于字符串池的机制,两者实际指向了同一对象 String a = "abcde"; String b = "abcde"; Console.WriteLine(Object.ReferenceEquals(a, b)); //在本书前面章节的例子已中有介绍,编译器会优化这样的代码 //所以这行代码等同于String c="abcde"; //由于字符串池,c和a还是指向同一对象 String c = "a" + "b" + "c" + "d" + "e"; Console.WriteLine(Object.ReferenceEquals(a, c)); //显式使用new来分配内存 //这时候字符串池机制不能起作用,d和a指向不同对象 Char[] chars ={ 'a', 'b', 'c', 'd', 'e' }; String d = new String(chars); Console.WriteLine(Object.ReferenceEquals(a, d)); Console.Read(); }
输出:
True
True
False
转载请注明出处: