字符、字符串和文本处理

字符

http://www.cnblogs.com/bitfan/archive/2010/11/25/1887590.html

在.NET Framework中,字符都是用16位unicode编码(utf-16)【视频 unicode字符编码查询(它这个编码方式是utf-16的Big Endian,具体可看最上面的链接unicode字符编码表(十进制0~127是与ASCII字符集(占7位)的一样(写的时候十六进制前面加0x就是0x0(0)~0x7F(127))   十进制19968~40895 CJK统一表意符号是主要的中文)】的(编译时用utf-16编码成2进制存到硬盘,程序运行时再用utf-16解码显示 代码中的字符串,在内存中相应的字节流就是用UTF-16编码过的  相关 ),也就是说所有字符都是占2个字节16位,这简化了国际化应用程序的开发。

unicode字符集有很多种编码方案,常用的有

utf-16:所有字符被编码成2个字节

utf-8:十进制小于128的字符被编码成1个字节(可表示欧美地区使用的字符),128~2047的字符被编码成2个字节(可表示欧洲和中东语言),大于2047的字符被编码成3个字节(可表示东亚地区的语言)

utf-32:所有字符都被编码成4个字节

unicode字符集还有个ASCII编码方案,这种编码只能将小于128的16位字符转换成单字节,而其他超过127的字符都会丢失。

GB2312等其他字符集(这些字符集可能只有一种同名编码方案)

可以调用Char类型的静态方法GetUnicodeCategory方法,这个方法返回的是System.Globalization.UnicodeCategory枚举类型的一个值。这个值指出该字符是控制字符、货币符号、小写字母、大写字母、标点符号、数字符号 还是其他Unicode标准定义的符号。 其他一些静态方法如IsDigit、IsLetter、IsUpper、IsControl、IsSymol等都在内部调用了GetUnicodeCategory,并简单返回true或false。

可以调用静态方法ToLowerInvariant或者ToUpperInvariant以一种忽略语言文化的方式,将一个字符转化为小写或大写。如果调用ToLower和ToUpper方法,在转换时要使用与线程相关的语言文化信息,这俩方法会在内部查询System.Threading.Thread类的静态CurrentCulture属性来获得的。还可以向这俩方法传递CultureInfo类的一个实例来具体指定一种语言文化。

 

System.String类型

一个String代表一个不可变的顺序字符集。String类型直接派生自Object,所以它是一个引用类型。因此String对象总是存在于堆上,永远不会跑到线程栈。许多编程语言都将String视为一个基元类型----可以再源代码中直接表示string s="hi"; 编译器将这些文本常量字符串放到模块的元数据中,并在运行时加载和引用它们。

对于换行符、回车符和退格符这样的特殊字符,C#采用的是C/C++开发人员熟悉的转义机制:

string s="hi\r\nthere";  包含回车符和换行符

string s="hi"+Environment.NewLine+"there";

对于如下由好几个文本常量字符串组成的字符串:

string s="hi"+" "+"there";   编译器会在编译时连接它们,最终只会将一个字符串放到模块的元数据中。

对于如下由好几个非文本常量字符串组成的字符串:

string s1="hi"; string s2="there"; string s=s1+s2;  这些字符串的连接会在运行时进行,这会在堆上创建多个字符串对象。

C#还提供了逐字字符串,通常用于指定文件或目录的路径,或与正则表达式配合使用:

string file="C:\\windows\\System32\\Notepad.exe";

string file=@"C:\windows\System32\Notepad.exe";

在字符串前添加@符号,使编译器知道字符串是一个逐字字符串,这告诉编译器将反斜杠视为文本常量,而不是转义符。

string对象最重要的一个事实就是,它是不可变的,也就是说字符串一经创建便不能更改,不能变长,变短或修改其中任何字符。

所以允许对一个字符串进行各种操作而不实质的改变字符串:

string s="hiworld"; if(s.ToUperInvariant().Substring(0,2).EndsWith("EXE")){...}

在此,ToUperInvariant()返回一个新的字符串,它没有修改s的字符,然后Substring(0,2)在ToUperInvariant()返回的新字符串的基础上又返回一个新字符串。 ToUperInvariant和Substring创建的两个临时字符串不会由应用程序代码长久的引用,垃圾回收器会在下次回收时回收它们的内存,如果执行大量的字符串操作,会在堆上创建大量的string对象,造成频繁的垃圾回收。

使字符串不可变,还意味着在操纵或访问一个字符串时不会发生线程同步问题。

 

比较字符串

.NET Framework使用System.Globalization.CultureInfo类型表示一个“语言/国家”对。如:en-US代表美国英语,en-AU代表澳大利亚英语,de-DE代表德国德语。

在CLR中,每个线程都关联了两个特殊属性,每个属性都引用一个CultureInfo对象:

CurrentUICulture属性:该属性用于获取要向用户显示的资源,当创建一个线程时,这个线程属性会被设置成一个CultureInfo对象,它标示了正在运行应用程序的windows的版本所用的语言,该语言是使用Win32的函数GetUserDefaultUILanguage来获取的,可通过控制面板的“区域和语言”对话框来设置。

CurrentCulture属性:该属性用于数字和日期格式化、字符串大小写以及字符串比较。

一般而言这俩属性被设为同一CultureInfo对象,即它们使用相同的语言/国家信息。然而,比如我们在美国运行一个应用程序需要用西班牙语来显示菜单及其他GUI元素,同时仍要正确显示美国的货币和日期格式。为此,线程的CurrentUICulture属性要引用一个CultureInfo对象,该对象要使用语言“es”来初始化。线程的CurrentCulture属性要引用另一个CultureInfo对象,该对象应使用“en-US”来初始化。

CultureInfo ci1=new CultureInfo("es");    CultureInfo ci1=new CultureInfo("en-US"); 

 

高效率构造字符串

从逻辑上讲StringBuilder对象包含一个字段,该字段引用了由Char结构构成的一个数组。可利用StringBuilder的各个成员来操纵这个字符数组,高效率地缩短字符串或更改字符串中的字符。如果字符串变大,超过了已分配的字符数组的大小,StringBuilder会自动分配一个新的、更大的数组,复制字符,并开始使用新数组。前一个数组会被垃圾回收。

StringBuilder有两值的一提的构造函数来设置其属性:1是最大容量(设置返回字符串toString()所能容纳的最大字符数,默认是Int32.MaxValue约为20亿,一般不需要修改)2是容量(由StringBuilder维护的字符数组的大小,默认是16,上面提到了超过了已分配的字符数组的大小会自动倍增分配一个更大的数组,原数组会被垃圾回收,这影响性能,所以一开始需设置一个合适大小的容量)

 

获取对象的字符串表示:ToString

System.Object实现的ToString只是返回对象所属类型的全名。这个值用处不大,但对许多不能提供有意义的字符串的类型来说,这也是一个合理的默认值。例如,一个FileStream或Hashtable对象的字符串表示应该是什么呢?

任何类型如果想提供一个合理的方式获取对象当前值的字符串表示,应该重写ToString方法。FCL内建的所有基类型都重写了它们的ToString方法,能返回一个符合语言文化的字符串。在Vistual Studio调试器中,鼠标移到一个变量上方,就会显示一条数据提示,提示的文本就是通过调用对象的ToString方法获取的。所以,在定义一个类时,可以重写ToString方法,以获得良好的调试支持。

 

指定具体的格式和语言文化

无参的ToString方法有两个问题,首先,调用者无法控制字符串的格式,其次,调用者不能方便的调用一种特定的语言文化。

为了使调用者能选择格式和语言文化,类型应该实现System.IFormattable接口:

public interface IFormattable{

  String ToString(String format,IFormatProvider formatprovider)

}

第一个参数format是一个特殊的字符串,作用是告诉方法该如何格式化对象,假如传递的格式化字符串无法被类型识别,类型应该抛出System.FormatException异常。 第二个参数formatprovider是实现了System.IFormatProvider接口的类型的实例,System.Globalization.CultureInfo类型就实现了它,用于指定具体的语言文化。

在FCL中,所有基类型都实现了 IFormattable接口,枚举类型都自动实现 IFormattable接口。

FCL中许多类型都能同时识别几种格式:

DateTime -> d短日期 D长日期 g表示常规 M月/日 s表示可排序(sortable) T长时间  u表示符合ISO 8601格式的协调世界时 U长日期格式的协调世界时 Y年/月

枚举类型 -> G表示常规 F标识(flag) D十进制 X十六进制

数值类型 -> C货币类型 D十进制 E表示科学技术法(指数)格式 F定点(fix-point)格式 G表示常规 N数字格式 P百分比格式 R往返行程格式 X十六进制

以下代码将以越南地区适用的货币格式来获取一个Decimal数值的字符串表示:

Decimal price=123.5M;

String s=price.ToString("C",new CultureInfo("vi-VN"));

如果不向ToString()传递参数或传递null ToString(null,null) 将默认是选择了常规格式 和 当前线程的语言文化。

 

将多个对象格式化成一个字符串

String s=String.Format(“On {0}, {1} is {2} years old”,new DateTime(2010,4,22,14,35,5),"Bill",7);

On 2010-4-22 14:35:05, Bill is 7 years old

在内部,Format方法会调用每个对象的ToString方法来获取对象的一个字符串表示。

String s=String.Format(“On {0:D}, {1} is {2:E} years old”,new DateTime(2010,4,22,14,35,5),"Bill",7);

On 2010年4月22日, Bill is 7.000000E+000 years old

采取在大括号内指定格式信息的方式,可对一个对象的格式化进行更多控制。

String类的静态Format方法的几个重载版本。一个版本实现了IFormatProvider接口的一个对象,允许使用由调用者指定的语言文化信息来格式化所有可替换的参数。

 

提供定制格式化器

我们可以定义一个方法,以便在任何对象需要格式化成一个字符串时,由String的Format方法来调用这个方法。也就是说,Format方法不是调用每个对象的ToString,而是调用我们自己定义的方法,从而按照我们自己希望的任何方式格式化部分或全部对象。它也同样适用于StringBulider的AppendFormat方法:

        protected void Page_Load(object sender, EventArgs e)

        {

            Label1.Text = string.Format(new BoldInt(), "{0} {1} {2}", "I am", 17, "years old.");

        }

 

        internal class BoldInt : IFormatProvider, ICustomFormatter

        {

            public object GetFormat(Type formatType) 

            {

                if (formatType==typeof(ICustomFormatter))

                {

                    return this;

                }

                return Thread.CurrentThread.CurrentUICulture.GetFormat(formatType);

            }

            public string Format(string format,object arg,IFormatProvider formatprovider)

            {

                string s;

                IFormattable formattable = arg as IFormattable;

                if (formattable == null)

                {

                    s = arg.ToString();

                }else {

                    s = formattable.ToString(format, formatprovider);

                    if (arg.GetType()==typeof(int))

                    {

                        return "<B style='color:red'>" + s + "</B>";

                    }

                }

                return s;

            }

        }

以上在Format方法(这是实现了ICustomFrmat接口里的Format方法)内部,进行了一些巧妙的操作,从而对字符串的格式化进行全面的控制。

 

解析字符串来获取对象:Parse

能解析一个字符串的任何类型都提供了一个名为Parse的public static方法。该方法获取一个string对象,并返回类型的一个实例。从某种意义上说,Parse扮演了工厂的角色。在FCL中,所有数值类型、DateTime、TimeSpan以及其他一些类型(比如SQL数据类型)均提供了Parse方法。

Parse方法有很多重载,最全的一种如下:

public static Int32 Parse(String s,NumberStyles style,IFormatProvider provider)

s就是要解析的字符串,System.Globalization.NumberStyles是一个枚举类型,IFormatProvider就是语言文化方面的

Int32 x=Int32.Parse(" 123",NumberStyle.None,null); 会抛出一个System.FormatExcept异常

如果要允许Parse跳过前导的空白字符,要像下面这样修改style参数:

Int32 x=Int32.Parse(" 123",NumberStyle.AllowLeadingWhite,null);

如何解析一个十六进制数字:

Int32 x=Int32.Parse("1A",NumberStyle.HexNumber,null);  26

DateTime类型提供的Parse方法的style参数是System.GlobalizationDateTimeStyles枚举类型

一些开发人员反映当他们的应用程序频繁调用Parse,而且Parse频繁抛出异常时(由于无效的用户输入)应用程序的性能显著下降,为此微软将提供Parse方法的类型,甚至IPAddress类型都增加了TryParse方法:

public static Boolean TryParse(String s,NumberStyles style,IFormatProvider provider,out Int32 result)

可以看出,这个方法返回true或false,指出传递的字符串是否能解析成一个Int32,如果返回true,以"传引用"的方式给result参数的变量将包含解析好的数值。

 

字符和字节的相互转换

在CLR中所有字符都是以16位Unicode码值的形式来表示的,当我们需要将一个字符串保存到文件中或者通过网络来传输它们,我们可以将16位值编码成一个压缩的字节数组,以后再将字节数组解码回一个16位值数组。

需要编码或解码一组字符的时,应获取从System.Text.Encoding派生的一个类的实例。Encoding是一个抽象基类,它提供了几个静态readonly属性(UTF8,Unicode[UTF-16],BigEndianUnicode,UTF32,UTF7,ASCII,Default),每个属性都返回从Encoding派生的一个类的实例,它决定应该用哪种方式编码/解码字符:

String s="Hi there";

Byte[] encodeBytes=Encoding.UTF8.GetBytes(s);  用utf-8编码字符

BitConvert.ToString(encodeBytes); 显示编码的字节值  46-69-20-74-68-65-72-65-2E

String decodeString=Encoding.UTF8.GetString(encodeBytes);  用utf-8解码字符

上面我们看到了写了两次Encoding.UTF8,但CLR会给我们优化,只会实例化一个实例,而实现同样的效果,可以实例化System.Text.UTF8Encoding,System.Text.UnicodeEncoding等,实例化几次就创建几个对象,会损坏性能。

Encoding还提供了一个静态的GetEncoding方法,它返回一个可以使用指定代码页进行编码或解码的对象,如Encoding.GetEncoding(“Shift-JIS”)或Encoding.GetEncoding(932)

当从一个流中读取字节时,使用上面对象的GetString方法有可能造成数据损坏,这是因为Encoding派生的所有类都不对多个方法调用之间的状态进行维护。可以使用Encoding.UTF8.GetEncoder(),Encoding.UTF8.GetDecoder()方法得到的对象来对字节块进行编码,解码:

http://technet.microsoft.com/zh-cn/library/system.text.utf8encoding.getencoder(VS.95).aspx

http://technet.microsoft.com/zh-cn/library/system.text.utf8encoding.getdecoder(VS.95).aspx           

            string s = "家里都有两条龙";

            char[] chars=s.ToCharArray();

            int bytelength = Encoding.UTF8.GetEncoder().GetByteCount(chars, 0, chars.Length, true);

            byte[] bytes = new byte[bytelength]; 

            Encoding.UTF8.GetEncoder().GetBytes(chars, 0, chars.Length, bytes, 0,true);

            Label4.Text += BitConverter.ToString(bytes);

            int charlength = Encoding.UTF8.GetDecoder().GetCharCount(bytes, 0, bytes.Length);

            char[] chars = new char[charlength];

            Encoding.UTF8.GetDecoder().GetChars(bytes,0,bytes.Length,chars,0);

            foreach (char c in chars)

            {

                Label3.Text += c.ToString();

            }

Base-64字符串的编码和解码

            byte[] byte=new byte[10];

            new Random().NextBytes(byte);   随机产生10个随机byte的数组

            Label5.Text = Convert.ToBase64String(byte);  参数只能是byte[]

            byte[] base64byte=Convert.FromBase64String(Label5.Text);  参数只能是base64格式的字符串

            Label6.Text = BitConverter.ToString(base64byte);

你可能感兴趣的:(字符串)