首页 > 代码库 > .NET Framework 中的字符编码
.NET Framework 中的字符编码
字符是可用多种不同方式表示的抽象实体。 字符编码是一种为受支持字符集中的每个字符进行配对的系统,配对时使用的是表示该字符的某些值。 例如,摩尔斯电码是一种为罗马字母表中的每个字符进行配对的字符编码,配对时使用的是适合在电报线路中传输的点和线模式。 计算机的字符编码将所支持字符集中的每个字符与代表该字符的数值进行配对。字符编码具有两个不同的组件:
编码器,将字符序列转换为数值序列(字节)。
解码器,将字节序列转换为字符序列。
字符编码描述了编码器和解码器的操作规则。 例如,UTF8Encoding 类描述了编码和解码 8 位 Unicode 传输格式 (UTF-8) 的规则,该格式使用一到四个字节来表示单个 Unicode 字符。 编码和解码过程还可以包括验证。 例如,UnicodeEncoding 类会检查所有代理,以确保它们组成有效的代理对。(代理对由两个字符组成,第一个字符具有范围从 U+D800 到 U+DBFF 的码位,后面的字符具有范围从 U+DC00 到 U+DFFF 的码位。)回退策略确定编码器如何处理无效字符或解码器如何处理无效字节。
警告 |
---|
.NET framework 编码类提供了一种存储和转换字符数据的方法。 不应使用这些类存储字符串形式的二进制数据。 根据所使用的编码,使用编码类将二进制数据转换为字符串格式可能引入意外行为并生成不准确或损坏的数据。 若要将二进制数据转换为字符串形式,请使用Convert.ToBase64String 方法。 |
面向公共语言运行时的应用程序使用编码器将受公共语言运行时支持的 Unicode 字符表示形式映射到其他编码架构中。 这些应用程序使用解码器将字符从非 Unicode 编码映射为 Unicode。
本主题包含以下几节:
在 .NET Framework 中编码
选择编码类
使用编码对象
选择回退策略
实现自定义回退策略
.NET Framework 中的所有字符编码类均继承自 System.Text.Encoding 类,后者是定义所有字符编码的常用功能的抽象类。 若要访问在 .NET Framework 中实现的单个编码对象,请执行以下操作:
使用 Encoding 类的静态属性,这些属性会返回表示 .NET Framework(ASCII、UTF-7、UTF-8、UTF-16 和 UTF-32)中提供的标准字符编码的对象。 例如,Encoding.Unicode 属性返回 UnicodeEncoding 对象。 每个对象都使用替换回退来处理它无法编码的字符串和无法解码的字节。有关更多信息,请参见Replacement Fallback部分。
调用编码的类构造函数。 可通过这种方式实例化 ASCII、UTF-7、UTF-8、UTF-16 和 UTF-32 编码的对象。 默认情况下,每个对象都使用替换回退来处理其无法编码的字符串和无法解码的字节,但您也可以指定引发异常。有关更多信息,请参见Replacement Fallback和Exception Fallback部分。
调用 Encoding.Encoding(Int32) 构造函数并向其传递一个表示编码的整数。 标准编码对象使用替换回退,而编码页和双字节字符集 (DBCS) 编码对象使用最佳回退来处理它们无法编码的字符和无法解码的字节。有关更多信息,请参见Best-Fit Fallback部分。
调用 Encoding.GetEncoding 方法,该方法返回 .NET Framework 中提供的任何标准、代码页或 DBCS 编码。 可通过重载为编码器和解码器指定回退对象。
说明 |
---|
Unicode 标准可为每个受支持脚本中的每个字符分配码位(一个数字)和名称。 例如,字符“A”由码位 U+0041 和名称“LATIN CAPITAL LETTER A”表示。 Unicode 传输格式 (UTF) 编码定义了将此码位编码为一个或多个字节序列的方式。 Unicode 编码方案简化了开发国际化应用程序的过程,因为它允许用单个编码表示任何字符集中的字符。应用程序开发人员不必再跟踪用于为特定语言或编写系统生成字符的编码方案,并且数据可以在全球各系统之间实现共享而不会被损坏。 .NET Framework 支持由 Unicode 标准定义的三种编码:UTF-8、UTF-16 和 UTF-32。 有关更多信息,请参见 Unicode home page(Unicode 标准)。 |
通过调用 Encoding.GetEncodings 方法,您可以检索有关 .NET Framework 中提供的所有编码的信息。 .NET Framework 支持下表中列出的字符编码系统。
编码 | 类 | 描述 | 优点/缺点 |
---|---|---|---|
ASCII | ASCIIEncoding | 使用字节较低的七位对有限范围内的字符进行编码。 | 由于此编码仅支持从 U+0000 到 U+007F 的字符值,因此,大多数情况下,它并不适用于国际化应用程序。 |
UTF-7 | UTF7Encoding | 将字符表示为 7 位 ASCII 字符序列。 非 ASCII Unicode 字符由 ASCII 字符的转义序列表示。 | UTF-7 支持协议,如电子邮件和新闻组协议。 但是,UTF-7 并非特别安全或可靠。 在某些情况下,更改一个位可能会完全改变对整个 UTF-7 字符串的解释。 在其他某些情况下,不同的 UTF-7 字符串可能编码成相同的文本。 对于包含非 ASCII 字符的序列,UTF-7 需要的空间要高于 UTF-8,且编码/解码速度也更慢。 因此,应该尽可能使用 UTF-8 而不是 UTF-7。 |
UTF-8 | UTF8Encoding | 将每个 Unicode 码位表示为包含一到四个字节的序列。 | UTF-8 支持 8 位数据大小,并且能够在许多现有操作系统上正常工作。 对于 ASCII 范围的字符,UTF-8 与 ASCII 编码相同,并且允许更宽的字符集。 但是,对于中文-日语-朝鲜语 (CJK) 脚本,UTF-8 可能要求每个字符使用三个字节,这可能导致数据大小超过 UTF-16。请注意,有时 ASCII 数据(如 HTML 标记)可能是 CJK 范围增大的原因。 |
UTF-16 | UnicodeEncoding | 将每个 Unicode 码位表示为一个或两个 16 位整数的序列。 虽然 Unicode 增补字符(U+10000 及更高)需要两个 UTF-16 代理码位,但最常见的 Unicode 字符只需要一个 UTF-16 码位。 同时支持 Little-Endian 和 Big-Endian 字节顺序。 | 由公共语言运行时使用的 UTF-16 编码表示 Char 和 String 值,而由 Windows 操作系统使用的 UTF-16 编码则表示 WCHAR 值。 |
UTF-32 | UTF32Encoding | 将每个 Unicode 码位表示为一个 32 位整数。 同时支持 Little-Endian 和 Big-Endian 字节顺序。 | 对于编码空间对其非常重要的操作系统,如果应用程序希望避免 UTF-16 编码在此类计算机上出现代理码位行为,则可以使用 UTF-32 编码。 仍可以使用多个 UTF-32 字符对屏幕上呈现的单个符号进行编码。 |
ANSI/ISO 编码 | 提供对各种代码页的支持。 在 Windows 操作系统上,代码页用于支持特定语言或语言组。 要查看列出受 .NET Framework 支持的代码页的表,请参见 Encoding 类。通过调用 Encoding.GetEncoding(Int32)方法,您可以检索特定代码页的编码对象。 | 代码页包含 256 个码位,并且从零开始。 在大多数代码页中,0 到 127 的码位表示 ASCII 字符集,而 128 到 255 之间的码位在代码页之间的差异很大。 例如,代码页 1252 为拉丁语书写系统(包括英语、德语和法语)提供字符。 代码页 1252 中的后 128 个码位均包含重音字符。 代码页 1253 提供在希腊语书写系统中所需的字符代码。 代码页 1253 中的后 128 个码位均包含希腊语字符。 因此,依赖 ANSI 代码页的应用程序不能将希腊语和德语存储在同一个文本流中,除非它包含指示所引用代码页的标识符。 | |
双字节字符集 (DBCS) 编码 | 支持包含超过 256 个字符的语言(如中文、日语和朝鲜语)。 在 DBCS 中,一对码位(双字节)表示一个字符。Encoding.IsSingleByte 属性对 DBCS 编码返回 false。 通过调用Encoding.GetEncoding(Int32) 方法,可以检索特定 DBCS 的编码对象。 | 在 DBCS 中,一对码位(双字节)表示一个字符。 应用程序在处理 DBCS 数据时,会同时处理 DBCS 字符的第一个字节(前导字节)以及紧跟其后的结尾字节。 因为一对双字节码位可表示不同的字符(取决于代码页),所以此方案依然不允许在同一数据流中融合两种语言(如日语和中文)。 |
这些编码使您能够使用 Unicode 字符,以及在旧应用程序中最常使用的编码。 此外,您还可以通过定义从 Encoding 派生的类并重写其成员来创建自定义编码。
如果您有机会选择您的应用程序要使用的编码,则应该使用 Unicode 编码,最好是 UTF8Encoding 或 UnicodeEncoding。(.NET Framework 还支持第三个 Unicode 编码,即 UTF32Encoding。)
如果您计划使用 ASCII 编码 (ASCIIEncoding),请改用 UTF8Encoding。 虽然这两个编码对于 ASCII 字符集来说是相同的,但是 UTF8Encoding 具有以下优点:
它可以表示每个 Unicode 字符,而 ASCIIEncoding 仅支持介于 U+0000 和 U+007F 之间的 Unicode 字符值。
它可以提供错误检测以及更好的安全性。
它已经过优化,以便尽可能提高速度且应该比任何其他编码更快速。 即使对于完全采用 ASCII 的内容,使用 UTF8Encoding 执行的操作也比使用 ASCIIEncoding 执行的操作速度更快。
只应对旧应用程序考虑使用 ASCIIEncoding。 但是,即使对旧应用程序而言,UTF8Encoding 可能也会因以下原因而成为更好的选择:
如果应用程序具有未严格采用 ASCII 的内容,并使用 ASCIIEncoding 对其进行编码,则每个非 ASCII 字符将编码为一个问号(?)。 如果应用程序随后对此数据进行解码,将会丢失信息。
如果应用程序具有未严格采用 ASCII 的内容,并使用 UTF8Encoding 对其进行编码,那么如果将结果解释为 ASCII,则结果看起来将不可理解。 但是,如果应用程序随后使用 UTF-8 解码器对此数据进行解码,则数据将成功进行往返。
在 Web 应用程序中,发送到客户端以响应 Web 请求的字符应反映客户端上使用的编码。 在大多数情况下,应该将 HttpResponse.ContentEncoding 属性设置为HttpRequest.ContentEncoding 属性返回的值,以便在用户期望的编码中显示文本。
编码器将字符串(最常见的是 Unicode 字符)转换为其数字(字节)等效值。 例如,您可以使用 ASCII 编码器将 Unicode 字符转换为 ASCII,以使这些字符显示在控制台中。若要执行该转换,请调用 Encoding.GetBytes 方法。 如果您想确定在执行编码以前需要多少字节来存储已编码的字符,则可以调用 GetByteCount 方法。
下面的示例使用单字节数组在两个独立的操作中编码字符串。 它所维护的索引指示下一组 ASCII 编码的字节在字节数组中的起始位置。 它调用ASCIIEncoding.GetByteCount(String) 方法来确保字节数组足以容纳编码的字符串。 然后,它调用 ASCIIEncoding.GetBytes(String, Int32, Int32, Byte[], Int32) 方法来对字符串中的字符进行编码。
using System;using System.Text;public class Example{ public static void Main() { string[] strings= { "This is the first sentence. ", "This is the second sentence. " }; Encoding asciiEncoding = Encoding.ASCII; // Create array of adequate size. byte[] bytes = new byte[49]; // Create index for current position of array. int index = 0; Console.WriteLine("Strings to encode:"); foreach (var stringValue in strings) { Console.WriteLine(" {0}", stringValue); int count = asciiEncoding.GetByteCount(stringValue); if (count + index >= bytes.Length) Array.Resize(ref bytes, bytes.Length + 50); int written = asciiEncoding.GetBytes(stringValue, 0, stringValue.Length, bytes, index); index = index + written; } Console.WriteLine("\nEncoded bytes:"); Console.WriteLine("{0}", ShowByteValues(bytes, index)); Console.WriteLine(); // Decode Unicode byte array to a string. string newString = asciiEncoding.GetString(bytes, 0, index); Console.WriteLine("Decoded: {0}", newString); } private static string ShowByteValues(byte[] bytes, int last ) { string returnString = " "; for (int ctr = 0; ctr <= last - 1; ctr++) { if (ctr % 20 == 0) returnString += "\n "; returnString += String.Format("{0:X2} ", bytes[ctr]); } return returnString; }}// The example displays the following output:// Strings to encode:// This is the first sentence.// This is the second sentence.// // Encoded bytes:// // 54 68 69 73 20 69 73 20 74 68 65 20 66 69 72 73 74 20 73 65// 6E 74 65 6E 63 65 2E 20 54 68 69 73 20 69 73 20 74 68 65 20// 73 65 63 6F 6E 64 20 73 65 6E 74 65 6E 63 65 2E 20// // Decoded: This is the first sentence. This is the second sentence.
解码器在字符数组或字符串中将反映特定字符编码的字节数组转换为字符集。 若要将字节数组解码为字符数组,可调用 Encoding.GetChars 方法。 若要将字节数组解码为字符串,可调用 GetString 方法。 如果您想确定在执行解码以前需要多少字符来存储已解码的字节,可调用 GetCharCount 方法。
下面的示例先对三个字符串进行编码,然后将它们解码为单个字符数组。 它所维护的索引指示下一组解码字符在字符数组中的起始位置。 它调用 GetCharCount 方法来确保字符数组足以容纳所有已解码的字符。 然后,它调用 ASCIIEncoding.GetChars(Byte[], Int32, Int32, Char[], Int32) 方法来解码字节数组。
using System;using System.Text;public class Example{ public static void Main() { string[] strings = { "This is the first sentence. ", "This is the second sentence. ", "This is the third sentence. " }; Encoding asciiEncoding = Encoding.ASCII; // Array to hold encoded bytes. byte[] bytes; // Array to hold decoded characters. char[] chars = new char[50]; // Create index for current position of character array. int index = 0; foreach (var stringValue in strings) { Console.WriteLine("String to Encode: {0}", stringValue); // Encode the string to a byte array. bytes = asciiEncoding.GetBytes(stringValue); // Display the encoded bytes. Console.Write("Encoded bytes: "); for (int ctr = 0; ctr < bytes.Length; ctr++) Console.Write(" {0}{1:X2}", ctr % 20 == 0 ? Environment.NewLine : "", bytes[ctr]); Console.WriteLine(); // Decode the bytes to a single character array. int count = asciiEncoding.GetCharCount(bytes); if (count + index >= chars.Length) Array.Resize(ref chars, chars.Length + 50); int written = asciiEncoding.GetChars(bytes, 0, bytes.Length, chars, index); index = index + written; Console.WriteLine(); } // Instantiate a single string containing the characters. string decodedString = new string(chars, 0, index - 1); Console.WriteLine("Decoded string: "); Console.WriteLine(decodedString); }}// The example displays the following output:// String to Encode: This is the first sentence.// Encoded bytes:// 54 68 69 73 20 69 73 20 74 68 65 20 66 69 72 73 74 20 73 65// 6E 74 65 6E 63 65 2E 20// // String to Encode: This is the second sentence.// Encoded bytes:// 54 68 69 73 20 69 73 20 74 68 65 20 73 65 63 6F 6E 64 20 73// 65 6E 74 65 6E 63 65 2E 20// // String to Encode: This is the third sentence.// Encoded bytes:// 54 68 69 73 20 69 73 20 74 68 65 20 74 68 69 72 64 20 73 65// 6E 74 65 6E 63 65 2E 20// // Decoded string:// This is the first sentence. This is the second sentence. This is the third sentence.
从 Encoding 派生的类的编码和解码方法设计用于完整的数据集;这意味着,在单个方法调用中会提供要编码或解码的所有数据。 但是,在某些情况下,流中需要数据,而要编码或解码的数据可能只来自单独的读取操作。 这就需要编码或解码操作以便从以前的调用中记住任何已保存的状态。 从 Encoder 和 Decoder 派生的类的方法能够处理跨多个方法调用的编码和解码操作。
特定编码的 Encoder 对象来自此编码的 Encoding.GetEncoder 属性。 特定编码的 Decoder 对象来自此编码的 Encoding.GetDecoder 属性。 请注意,对于解码操作,虽然从Decoder 派生的类包括 Decoder.GetChars 方法,但这些类不具有对应于 Encoding.GetString 的方法。
下面的示例说明了使用 Encoding.GetChars 和 Decoder.GetChars 方法解码 Unicode 字节数组之间的差异。 该示例将包含某些 Unicode 字符的字符串编码为文件,然后使用这两种解码方法一次性解码其中十个字节。 因为代理对出现在第十和第十一个字节中,所以在单独的方法调用中对其解码。 如输出所示,Encoding.GetChars 方法不能正确解码字节,而是用 U+FFFD (REPLACEMENT CHARACTER) 替换这些字节。 另一方面,Decoder.GetChars 方法能够成功地解码字节数组以便获得原始字符串。
using System;using System.IO;using System.Text;public class Example{ public static void Main() { // Use default replacement fallback for invalid encoding. UnicodeEncoding enc = new UnicodeEncoding(true, false, false); // Define a string with various Unicode characters. string str1 = "AB YZ 19 \uD800\udc05 \u00e4"; str1 += "Unicode characters. \u00a9 \u010C s \u0062\u0308"; Console.WriteLine("Created original string...\n"); // Convert string to byte array. byte[] bytes = enc.GetBytes(str1); FileStream fs = File.Create(@".\characters.bin"); BinaryWriter bw = new BinaryWriter(fs); bw.Write(bytes); bw.Close(); // Read bytes from file. FileStream fsIn = File.OpenRead(@".\characters.bin"); BinaryReader br = new BinaryReader(fsIn); const int count = 10; // Number of bytes to read at a time. byte[] bytesRead = new byte[10]; // Buffer (byte array). int read; // Number of bytes actually read. string str2 = String.Empty; // Decoded string. // Try using Encoding object for all operations. do { read = br.Read(bytesRead, 0, count); str2 += enc.GetString(bytesRead, 0, read); } while (read == count); br.Close(); Console.WriteLine("Decoded string using UnicodeEncoding.GetString()..."); CompareForEquality(str1, str2); Console.WriteLine(); // Use Decoder for all operations. fsIn = File.OpenRead(@".\characters.bin"); br = new BinaryReader(fsIn); Decoder decoder = enc.GetDecoder(); char[] chars = new char[50]; int index = 0; // Next character to write in array. int written = 0; // Number of chars written to array. do { read = br.Read(bytesRead, 0, count); if (index + decoder.GetCharCount(bytesRead, 0, read) - 1 >= chars.Length) Array.Resize(ref chars, chars.Length + 50); written = decoder.GetChars(bytesRead, 0, read, chars, index); index += written; } while (read == count); br.Close(); // Instantiate a string with the decoded characters. string str3 = new String(chars, 0, index); Console.WriteLine("Decoded string using UnicodeEncoding.Decoder.GetString()..."); CompareForEquality(str1, str3); } private static void CompareForEquality(string original, string decoded) { bool result = original.Equals(decoded); Console.WriteLine("original = decoded: {0}", original.Equals(decoded, StringComparison.Ordinal)); if (! result) { Console.WriteLine("Code points in original string:"); foreach (var ch in original) Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4")); Console.WriteLine(); Console.WriteLine("Code points in decoded string:"); foreach (var ch in decoded) Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4")); Console.WriteLine(); } }}// The example displays the following output:// Created original string...// // Decoded string using UnicodeEncoding.GetString()...// original = decoded: False// Code points in original string:// 0041 0042 0020 0059 005A 0020 0031 0039 0020 D800 DC05 0020 00E4 0055 006E 0069 0063 006F// 0064 0065 0020 0063 0068 0061 0072 0061 0063 0074 0065 0072 0073 002E 0020 00A9 0020 010C// 0020 0073 0020 0062 0308// Code points in decoded string:// 0041 0042 0020 0059 005A 0020 0031 0039 0020 FFFD FFFD 0020 00E4 0055 006E 0069 0063 006F// 0064 0065 0020 0063 0068 0061 0072 0061 0063 0074 0065 0072 0073 002E 0020 00A9 0020 010C// 0020 0073 0020 0062 0308// // Decoded string using UnicodeEncoding.Decoder.GetString()...// original = decoded: True
当某个方法尝试编码或解码字符而相应的映射不存在时,它必须执行回退策略,以确定应如何处理失败的映射。 有三种类型的回退策略:
最佳回退
替换回退
异常回退
重要事项 |
---|
如果 Unicode 字符无法映射到特定代码页编码,则会出现编码操作中最常见的问题。 如果无法将无效字节序列转换为有效的 Unicode 字符,则会出现解码操作中最常见的问题。 由于这些原因,您应该了解编码对象会使用哪种回退策略。 如果可能,您应该在实例化对象时指定编码对象使用的回退策略。 |
最佳回退
当字符在目标编码中不具有精确匹配项时,编码器可以尝试将其映射到类似的字符。(最佳回退主要是编码而不是解码问题。 有极少数代码页包含无法成功映射到 Unicode 的字符。)最佳回退对于代码页以及由 Encoding.GetEncoding(Int32) 和 Encoding.GetEncoding(String) 重载检索的双字符集编码来说是默认的。
说明 |
---|
从理论上讲,.NET Framework 中提供的 Unicode 编码类(UTF8Encoding、UnicodeEncoding 和 UTF32Encoding)支持每个字符集中的每个字符,因此它们可用于消除最佳回退问题。 |
最佳策略因代码页的不同而有所不同,此处不详细说明。 例如,对于有些代码页,全角拉丁字符会映射成更常见的半角拉丁字符。 对于其他代码页,不进行这种映射。 即使依据积极的最佳策略,在有些编码中仍然找不到适合某些字符的映射。 例如,汉字不能适当地映射到代码页 1252。 在这种情况下,将使用替换字符串。 默认情况下,此字符串只是一个问号 (U+003F)。
以下示例使用代码页 1252(用于西欧语言的 Windows 代码页)来说明最佳映射及其缺点。 Encoding.GetEncoding(Int32) 方法用于检测代码页 1252 的编码对象。 默认情况下,它对其不支持的 Unicode 字符使用最佳映射。 该示例实例化包含三个非 ASCII 字符(CIRCLED LATIN CAPITAL LETTER S (U+24C8)、SUPERSCRIPT FIVE (U+2075) 和 INFINITY (U+221E))的字符串,这些字符串用空格分隔。 正如示例输出所示,在编码字符串时,可用 QUESTION MARK (U+003F)、DIGIT FIVE (U+0035) 和 DIGIT EIGHT (U+0038) 来替换三种原始的非空格字符。 对于不受支持的 INFINITY 字符来说,DIGIT EIGHT 是最差的替换项,并且 QUESTION MARK 指示没有映射可用于原始字符。
using System;using System.Text;public class Example{ public static void Main() { // Get an encoding for code page 1252 (Western Europe character set). Encoding cp1252 = Encoding.GetEncoding(1252); // Define and display a string. string str = "\u24c8 \u2075 \u221e"; Console.WriteLine("Original string: " + str); Console.Write("Code points in string: "); foreach (var ch in str) Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4")); Console.WriteLine("\n"); // Encode a Unicode string. Byte[] bytes = cp1252.GetBytes(str); Console.Write("Encoded bytes: "); foreach (byte byt in bytes) Console.Write("{0:X2} ", byt); Console.WriteLine("\n"); // Decode the string. string str2 = cp1252.GetString(bytes); Console.WriteLine("String round-tripped: {0}", str.Equals(str2)); if (! str.Equals(str2)) { Console.WriteLine(str2); foreach (var ch in str2) Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4")); } }}// The example displays the following output:// Original string: ? ? ∞// Code points in string: 24C8 0020 2075 0020 221E// // Encoded bytes: 3F 20 35 20 38// // String round-tripped: False// ? 5 8// 003F 0020 0035 0020 0038
对于将 Unicode 数据编码为代码页数据的 Encoding 对象来说,最佳映射是默认行为,并且某些旧应用程序依赖此行为。 但是,出于安全原因,大多数新应用程序应避免采用最佳映射行为。 例如,应用程序不应对域名进行最佳编码。
说明 |
---|
您还可以为编码实现自定义最佳回退映射。 有关详细信息,请参阅Implementing a Custom Fallback Strategy部分 |
如果最佳回退是编码对象的默认行为,那么在您通过调用 Encoding.GetEncoding(Int32, EncoderFallback, DecoderFallback) 或 Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) 重载来检索 Encoding 对象时,可以选择另一种回退策略。 下节包括一个用星号 (*) 替换无法映射到代码页 1252 的每个字符的示例。
using System;using System.Text;public class Example{ public static void Main() { Encoding cp1252r = Encoding.GetEncoding(1252, new EncoderReplacementFallback("*"), new DecoderReplacementFallback("*")); string str1 = "\u24C8 \u2075 \u221E"; Console.WriteLine(str1); foreach (var ch in str1) Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4")); Console.WriteLine(); byte[] bytes = cp1252r.GetBytes(str1); string str2 = cp1252r.GetString(bytes); Console.WriteLine("Round-trip: {0}", str1.Equals(str2)); if (! str1.Equals(str2)) { Console.WriteLine(str2); foreach (var ch in str2) Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4")); Console.WriteLine(); } }}// The example displays the following output:// ? ? ∞// 24C8 0020 2075 0020 221E// Round-trip: False// * * *// 002A 0020 002A 0020 002A
替换回退
当字符在目标方案中不具有精确匹配项(不存在其可以映射到的适当字符)时,应用程序可指定替换字符或字符串。 这是 Unicode 解码器的默认行为,此解码器用 REPLACEMENT_CHARACTER (U+FFFD) 替换它无法解码的任何两字节序列。 它还是 ASCIIEncoding 类的默认行为,该类用问号替换它无法编码或解码的任何字符。 以下示例说明前面示例中 Unicode 字符串的字符替换。 如输出所示,无法解码为 ASCII 字节值的每个字符都可通过 0x3F(它是问号的 ASCII 代码)进行替换。
using System;using System.Text;public class Example{ public static void Main() { Encoding enc = Encoding.ASCII; string str1 = "\u24C8 \u2075 \u221E"; Console.WriteLine(str1); foreach (var ch in str1) Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4")); Console.WriteLine("\n"); // Encode the original string using the ASCII encoder. byte[] bytes = enc.GetBytes(str1); Console.Write("Encoded bytes: "); foreach (var byt in bytes) Console.Write("{0:X2} ", byt); Console.WriteLine("\n"); // Decode the ASCII bytes. string str2 = enc.GetString(bytes); Console.WriteLine("Round-trip: {0}", str1.Equals(str2)); if (! str1.Equals(str2)) { Console.WriteLine(str2); foreach (var ch in str2) Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4")); Console.WriteLine(); } }}// The example displays the following output:// ? ? ∞// 24C8 0020 2075 0020 221E// // Encoded bytes: 3F 20 3F 20 3F// // Round-trip: False// ? ? ?// 003F 0020 003F 0020 003F
.NET Framework 包括 EncoderReplacementFallback 和 DecoderReplacementFallback 类,如果字符在编码或解码操作中不能完全映射,这些类将采用替换字符串。 默认情况下,此替换字符串是一个问号,但可以调用类构造函数重载来选择其他字符串。 通常,替换字符串是单个字符,虽然并不要求如此。 通过实例化将星号 (*) 用作替换字符串的EncoderReplacementFallback 对象,以下示例更改了代码页 1252 编码器的行为。
using System;using System.Text;public class Example{ public static void Main() { Encoding cp1252r = Encoding.GetEncoding(1252, new EncoderReplacementFallback("*"), new DecoderReplacementFallback("*")); string str1 = "\u24C8 \u2075 \u221E"; Console.WriteLine(str1); foreach (var ch in str1) Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4")); Console.WriteLine(); byte[] bytes = cp1252r.GetBytes(str1); string str2 = cp1252r.GetString(bytes); Console.WriteLine("Round-trip: {0}", str1.Equals(str2)); if (! str1.Equals(str2)) { Console.WriteLine(str2); foreach (var ch in str2) Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4")); Console.WriteLine(); } }}// The example displays the following output:// ? ? ∞// 24C8 0020 2075 0020 221E// Round-trip: False// * * *// 002A 0020 002A 0020 002A
说明 |
---|
您还可以为编码实现替换类。 有关详细信息,请参阅Implementing a Custom Fallback Strategy部分 |
除了 QUESTION MARK (U+003F) 以外,Unicode REPLACEMENT CHARACTER (U+FFFD) 通常用作替换字符串,尤其是在解码无法成功转换为 Unicode 字符的字节序列时。 但是,您可以自由选择任何替换字符串,并且此字符串可以包含多个字符。
异常回退
如果编码器不能对字符集进行编码,它会引发 EncoderFallbackException;如果解码器不能解码字节数组,它会引发 DecoderFallbackException,而不是提供最佳回退或替换字符串。 若要在编码或解码操作中引发异常,可分别向 Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) 方法提供一个 EncoderExceptionFallback 对象和一个 DecoderExceptionFallback 对象。 以下示例使用 ASCIIEncoding 类说明异常回退。
using System;using System.Text;public class Example{ public static void Main() { Encoding enc = Encoding.GetEncoding("us-ascii", new EncoderExceptionFallback(), new DecoderExceptionFallback()); string str1 = "\u24C8 \u2075 \u221E"; Console.WriteLine(str1); foreach (var ch in str1) Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4")); Console.WriteLine("\n"); // Encode the original string using the ASCII encoder. byte[] bytes = {}; try { bytes = enc.GetBytes(str1); Console.Write("Encoded bytes: "); foreach (var byt in bytes) Console.Write("{0:X2} ", byt); Console.WriteLine(); } catch (EncoderFallbackException e) { Console.Write("Exception: "); if (e.IsUnknownSurrogate()) Console.WriteLine("Unable to encode surrogate pair 0x{0:X4} 0x{1:X3} at index {2}.", Convert.ToUInt16(e.CharUnknownHigh), Convert.ToUInt16(e.CharUnknownLow), e.Index); else Console.WriteLine("Unable to encode 0x{0:X4} at index {1}.", Convert.ToUInt16(e.CharUnknown), e.Index); return; } Console.WriteLine(); // Decode the ASCII bytes. try { string str2 = enc.GetString(bytes); Console.WriteLine("Round-trip: {0}", str1.Equals(str2)); if (! str1.Equals(str2)) { Console.WriteLine(str2); foreach (var ch in str2) Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4")); Console.WriteLine(); } } catch (DecoderFallbackException e) { Console.Write("Unable to decode byte(s) "); foreach (byte unknown in e.BytesUnknown) Console.Write("0x{0:X2} "); Console.WriteLine("at index {0}", e.Index); } }}// The example displays the following output:// ? ? ∞// 24C8 0020 2075 0020 221E// // Exception: Unable to encode 0x24C8 at index 0.
说明 |
---|
您还可以为编码操作实现自定义异常处理程序。 有关详细信息,请参阅Implementing a Custom Fallback Strategy部分 |
EncoderFallbackException 和 DecoderFallbackException 对象提供有关导致异常的条件的信息:
EncoderFallbackException 对象包括一个 IsUnknownSurrogate 方法,以说明字符或无法编码的字符都表示一种未知的代理对(在这种情况下,此方法返回 true)或一种未知的单个字符(在这种情况下,此方法返回 false)。 代理对中的字符来自 EncoderFallbackException.CharUnknownHigh 和EncoderFallbackException.CharUnknownLow 属性。 未知单个字符来自 EncoderFallbackException.CharUnknown 属性。 EncoderFallbackException.Index 属性指示字符串中在其处找到无法编码的第一个字符的位置。
DecoderFallbackException 对象包括返回无法解码的字节数组的 BytesUnknown 属性。 DecoderFallbackException.Index 属性指示未知字节的起始位置。
虽然 EncoderFallbackException 和 DecoderFallbackException 对象提供有关异常的充足诊断信息,但它们不提供对编码或解码缓冲区的访问权限。 因此,这些对象不允许在编码或解码方法中替换或更正无效数据。
除了通过代码页在内部实现的最佳映射以外,.NET Framework 还包括以下用于实现回退策略的类:
使用 EncoderReplacementFallback 和 EncoderReplacementFallbackBuffer 来替换编码操作中的字符。
使用 DecoderReplacementFallback 和 DecoderReplacementFallbackBuffer 来替换解码操作中的字符。
使用 EncoderExceptionFallback 和 EncoderExceptionFallbackBuffer 在无法编码字符时引发 EncoderFallbackException。
使用 DecoderExceptionFallback 和 DecoderExceptionFallbackBuffer 在无法解码字符时引发 DecoderFallbackException。
此外,您还可以按照下列步骤实现使用最佳回退、替代回退或异常回退的自定义解决方案:
从 EncoderFallback 派生一个用于执行编码操作的类,从 DecoderFallback 派生一个用于执行解码操作的类。
从 EncoderFallbackBuffer 派生一个用于执行编码操作的类,从 DecoderFallbackBuffer 派生一个用于执行解码操作的类。
对于异常回退,如果预定义的 EncoderFallbackException 和 DecoderFallbackException 类无法满足您的需要,可从异常对象(如 Exception 或ArgumentException)派生一个类。
从 EncoderFallback 或 DecoderFallback 派生
若要实现自定义回退解决方案,您必须创建一个从 EncoderFallback 继承的类以执行编码操作,创建一个从 DecoderFallback 继承的类以执行解码操作。 这些类的实例将传递给 Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) 方法并在编码类和回退实现之间充当中介。
在为编码器或解码器创建自定义回退解决方案时,必须实现以下成员:
EncoderFallback.MaxCharCount 或 DecoderFallback.MaxCharCount 属性,用于返回最佳、替代或异常回退可以返回的最大可能字符数以便替换单个字符。 对于自定义异常回退,其值是零。
EncoderFallback.CreateFallbackBuffer 或 DecoderFallback.CreateFallbackBuffer 方法,用于返回您的自定义 EncoderFallbackBuffer 或 DecoderFallbackBuffer 实现。 当此方法遇到不能成功编码的第一个字符时,编码器会调用它,而当此方法遇到不能成功解码的第一个字节时,解码器会调用它。
从 EncoderFallbackBuffer 或 DecoderFallbackBuffer 派生
若要实现自定义回退解决方案,还必须创建一个从 EncoderFallbackBuffer 继承的类以执行编码操作,创建一个从 DecoderFallbackBuffer 继承的类以执行解码操作。 这些类的实例是由 EncoderFallback 和 DecoderFallback 类的 CreateFallbackBuffer 方法返回的。 当 EncoderFallback.CreateFallbackBuffer 方法遇到不能成功编码的第一个字符时,编码器会调用它,而当 DecoderFallback.CreateFallbackBuffer 方法遇到不能成功解码的一个或多个字节时,解码器会调用它。 EncoderFallbackBuffer 和DecoderFallbackBuffer 类提供回退实现。 每个实例都表示一个包含回退字符的缓冲区,这些字符将替换无法编码的字符或无法解码的字节序列。
在为编码器或解码器创建自定义回退解决方案时,必须实现以下成员:
EncoderFallbackBuffer.Fallback 或 DecoderFallbackBuffer.Fallback 方法。 EncoderFallbackBuffer.Fallback 由编码器调用,以便为回退缓冲区提供有关它无法编码的字符的信息。 因为要编码的字符可以是代理对,所以会重载此方法。 会将一个重载传递给字符串中要编码的字符及其索引。 会将第二个重载传递给字符串中的高和低代理及其索引。 DecoderFallbackBuffer.Fallback 方法由解码器调用,以便为回退缓冲区提供有关无法解码的字节的信息。 会将此方法传递给它无法解码的字节数组以及第一个字节的索引。 如果回退缓冲区可以提供最佳或替换字符,那么回退方法应返回 true; 否则应返回 false。 对于异常回退,回退方法应引发异常。
EncoderFallbackBuffer.GetNextChar 或 DecoderFallbackBuffer.GetNextChar 方法,编码器或解码器会反复调用这两种方法,以便从回退缓冲区中获取下一个字符。在返回所有回退字符后,此方法应返回 U+0000。
EncoderFallbackBuffer.Remaining 或 DecoderFallbackBuffer.Remaining 属性,可返回回退缓冲区中的剩余字符数。
EncoderFallbackBuffer.MovePrevious 或 DecoderFallbackBuffer.MovePrevious 方法,用于将回退缓冲区中的当前位置移动到前一个字符。
EncoderFallbackBuffer.Reset 或 DecoderFallbackBuffer.Reset 方法,用于重新初始化回退缓冲区。
如果回退实现是最佳回退或替换回退,则从 EncoderFallbackBuffer 和 DecoderFallbackBuffer 派生的类仍会保留两个专用实例字段:缓冲区中确切的字符数以及缓冲区中要返回的下一个字符的索引。
EncoderFallback 示例
前面的示例使用替换回退来用星号 (*) 替换没有与 ASCII 字符对应的 Unicode 字符。 以下示例改用自定义最佳回退实现来提供非 ASCII 字符的更好映射。
以下代码定义一个名为 CustomMapper(从 EncoderFallback 派生)的类来处理非 ASCII 字符的最佳映射。 此类的 CreateFallbackBuffer 方法返回一个CustomMapperFallbackBuffer 对象,以提供 EncoderFallbackBuffer 实现。 CustomMapper 类使用 Dictionary<TKey, TValue> 对象存储不受支持的 Unicode 字符(键值)的映射及其相对应的 8 位字符(存储在 64 位整数的两个连续字节中)。 为了使此映射可用于回退缓冲区,会将 CustomMapper 实例作为参数传递给CustomMapperFallbackBuffer 类构造函数。 因为最长映射是 Unicode 字符 U+221E 的字符串“INF”,所以 MaxCharCount 属性会返回 3。
public class CustomMapper : EncoderFallback{ public string DefaultString; internal Dictionary<ushort, ulong> mapping; public CustomMapper() : this("*") { } public CustomMapper(string defaultString) { this.DefaultString = defaultString; // Create table of mappings mapping = new Dictionary<ushort, ulong>(); mapping.Add(0x24C8, 0x53); mapping.Add(0x2075, 0x35); mapping.Add(0x221E, 0x49004E0046); } public override EncoderFallbackBuffer CreateFallbackBuffer() { return new CustomMapperFallbackBuffer(this); } public override int MaxCharCount { get { return 3; } } }
以下代码定义从 EncoderFallbackBuffer 派生的 CustomMapperFallbackBuffer 类。 包含最佳映射且在 CustomMapper 实例中定义的字典来自其类构造函数。 如果在映射字典中定义了 ASCII 编码器无法编码的任何 Unicode 字符,那么其 Fallback 方法会返回 true;否则,返回 false。 对于每个回退,专用 count 变量指示仍然要返回的字符数,专用 index 变量指示字符串缓冲区中要返回的下一个字符 charsToReturn 的位置。
public class CustomMapperFallbackBuffer : EncoderFallbackBuffer{ int count = -1; // Number of characters to return int index = -1; // Index of character to return CustomMapper fb; string charsToReturn; public CustomMapperFallbackBuffer(CustomMapper fallback) { this.fb = fallback; } public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index) { // Do not try to map surrogates to ASCII. return false; } public override bool Fallback(char charUnknown, int index) { // Return false if there are already characters to map. if (count >= 1) return false; // Determine number of characters to return. charsToReturn = String.Empty; ushort key = Convert.ToUInt16(charUnknown); if (fb.mapping.ContainsKey(key)) { byte[] bytes = BitConverter.GetBytes(fb.mapping[key]); int ctr = 0; foreach (var byt in bytes) { if (byt > 0) { ctr++; charsToReturn += (char) byt; } } count = ctr; } else { // Return default. charsToReturn = fb.DefaultString; count = 1; } this.index = charsToReturn.Length - 1; return true; } public override char GetNextChar() { // We‘ll return a character if possible, so subtract from the count of chars to return. count--; // If count is less than zero, we‘ve returned all characters. if (count < 0) return ‘\u0000‘; this.index--; return charsToReturn[this.index + 1]; } public override bool MovePrevious() { // Original: if count >= -1 and pos >= 0 if (count >= -1) { count++; return true; } else { return false; } } public override int Remaining { get { return count < 0 ? 0 : count; } } public override void Reset() { count = -1; index = -1; }}
以下代码随后会实例化 CustomMapper 对象并将其实例传递给 Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) 方法。 该输出指示最佳回退实现成功处理了原始字符串中的三个非 ASCII 字符。
using System;using System.Collections.Generic;using System.Text;class Program{ static void Main() { Encoding enc = Encoding.GetEncoding("us-ascii", new CustomMapper(), new DecoderExceptionFallback()); string str1 = "\u24C8 \u2075 \u221E"; Console.WriteLine(str1); for (int ctr = 0; ctr <= str1.Length - 1; ctr++) { Console.Write("{0} ", Convert.ToUInt16(str1[ctr]).ToString("X4")); if (ctr == str1.Length - 1) Console.WriteLine(); } Console.WriteLine(); // Encode the original string using the ASCII encoder. byte[] bytes = enc.GetBytes(str1); Console.Write("Encoded bytes: "); foreach (var byt in bytes) Console.Write("{0:X2} ", byt); Console.WriteLine("\n"); // Decode the ASCII bytes. string str2 = enc.GetString(bytes); Console.WriteLine("Round-trip: {0}", str1.Equals(str2)); if (! str1.Equals(str2)) { Console.WriteLine(str2); foreach (var ch in str2) Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4")); Console.WriteLine(); } }}
MSDN参考地址:
http://msdn.microsoft.com/zh-cn/library/ms404377(v=vs.110).aspx
.NET Framework 中的字符编码