65.9K
CodeProject 正在变化。 阅读更多。
Home

在 .NET 中读写 Unicode 数据

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (51投票s)

2015 年 3 月 12 日

CPOL

32分钟阅读

viewsIcon

135761

downloadIcon

2954

本文面向初学者,介绍如何从不同数据源(包括文本文件、用户输入、数据库)读写 Unicode 数据。文章还将演示如何在 .NET 应用程序(包括 ASP.NET Web 应用程序)中显示这些数据。

引言

本文旨在帮助初学者理解“Unicode”这一概念,以及那些经常提问“如何存储 [非英语或非 ASCII] 语言到数据库并能正确取回”的用户。回想起几个月前,我也曾遇到过同样的情况,当时大多数问题都围绕着同一个主题……“如何从数据库中获取 [非 ASCII] 语言的数据并在应用程序中打印出来”。这篇文章就是为了解答这些问题,帮助这些用户和初学者程序员。

本文将让你清晰地理解 Unicode 是什么,为什么现在(以及它被创造出来的那一天起)被广泛使用。它将解释其几种类型(如 UTF-8、UTF-16 及其区别,应该使用哪种以及为什么),然后我将介绍如何在不同的 .NET 应用程序中使用这些字符。请注意,我还会使用 ASP.NET Web 应用程序来演示在 Web 环境下的场景。.NET 框架支持所有这些编码和代码页,使你能够与支持 Unicode 标准的各种产品共享数据。.NET 提供了多个类,让你能够基于 Unicode 字符创建支持全球语言的应用程序。

最后,我将使用一个数据库示例(我将使用 MS SQL Server)来展示如何从数据库读写数据;对我来说,这非常简单。完成之后,你可以下载并在你的机器上执行命令来亲自测试 Unicode 字符……现在就开始吧。

我不会详细介绍 Unicode 本身,而是专注于 .NET 对 Unicode 的实现。另外请注意,本文中的字符数字值是以数值(十进制)形式表示的,而不是 U+XXXX 十六进制形式。文末,我也会展示如何将十进制值转换为十六进制值。

Unicode 入门

Unicode 究竟是什么?

Unicode 是一个字符编码标准。你可以把它想象成一个标准,用于将每个字符转换为其数学符号,并将每个数学符号转换回其字符表示;计算机只能存储二进制数据,这就是为什么非二进制数据(数字)需要被转换为二进制表示才能存储在机器上。也就是说,每个字符都有一个单独分配给它的数字,用于标识要显示的字符。这个数字存储在内存中,计算机处理字节存储和其他事情,Unicode 不用于在内存中存储字节,它仅仅为每个字符提供一个数字。

在过去,开发者和程序员可用的不同编码方案来表示不同语言的数据的方案并不多;尽管那是因为应用程序的全球化在当时并不普遍。只使用英语,最初的代码页包含了表示和处理英语字母(小写和大写)以及一些特殊字符编码和解码的代码。ASCII 就是其中之一。在 ASCII 时代,它使用 7 位数据编码 128 个英文字符,然后使用最后一位来表示一些特殊字符。ASCII 不仅包含文本编码,还包含文本应如何渲染的指令等;其中许多现在已经不再使用。这是当时最广泛使用的标准,因为它满足了当时的需求。

随着计算机的普及,许多开发者希望他们的应用程序能够以对本地用户友好的版本提供,因此需要一个新的标准,否则每个开发者都可以创建自己的代码页来表示不同的字符,但这将消除机器之间的统一性。Unicode 早在 20 世纪 80 年代末就已出现(见维基百科的历史部分),但由于其体积庞大(每个字符 2 字节)而未被广泛使用。它比 ASCII 编码结构能够表示更多的字符。Unicode 支持 65,536 个字符,足以支持当今世界上所有的字符。因此,Unicode 被广泛使用,以支持全球所有字符,并确保从一台机器发送的字符能够正确映射回字符串,并且不会丢失数据(数据丢失是指句子未能正确渲染回来)。

简单来说,Unicode 是用于将不同字符映射到其数字表示的标准。Unicode 允许开发者使用数字数据,并将它们映射到可以用来在屏幕上表示字符的字形或字体。一个字符可以映射到一个数字,Unicode 会处理机器中的字节表示,然而一些其他的编码和映射机制被用来在应用程序和框架中实现 Unicode。大多数框架(包括且尤其重要的是 .NET 框架和 Java)都支持 Unicode 标准。

编码和映射

Unicode 是由 Unicode Consortium 定义的实际标准。现在需要实现 Unicode 标准以供使用。Unicode 提供了两种方法来实现字符到字节的编码以及将它们映射到数字表示。Unicode 指定了以下被使用的映射和编码:

  1. UTF-8
  2. UTF-16
  3. UTF-32

Unicode 中还有一些其他过时和已弃用的编码类型:

  1. UTF-1
    UTF-8 的前身
  2. UTF-7
    用于电子邮件,不再是 Unicode 标准的一部分。
  3. UTF-EBCDIC
    为兼容 EBCDIC 而开发,但不再是 Unicode 标准的一部分。

下一节将讨论这些编码和映射类型。

深入了解 Unicode 编码?

初学者常常会遇到 **UTF-8**、**UTF-16** 和 **UTF-32**,然后才想到 Unicode,并认为它们是不同的东西……嗯,并非如此。实际的东西只是 Unicode;一个标准。UTF-8 和 UTF-16 是为不同大小的编码方案命名的……UTF-8 是 1 字节(但请记住,在需要时它也可以扩展到 2 字节、3 字节或 4 字节,在本篇文章的最后,我会告诉你应该使用哪种方案以及为什么,所以请务必读到最后)等等。

UTF-8

UTF-8 是一种可变长度的 Unicode 编码类型,默认情况下是 8 位(1 字节),但可以扩展。这种字符编码方案可以容纳所有字符(因为它可以在多个字节之间扩展)。它的设计目的是为了与 ASCII 向后兼容;适用于不支持 Unicode 的机器。该标准可以用于表示前 128 个字符的 ASCII 码,然后在接下来的 1920 个字符中,它主要表示最常用的全球语言,如拉丁语、阿拉伯语、希腊语等,然后所有剩余的字符和码点都可以用来表示其余的字符……(引自 UTF-8 维基百科文章)

UTF-16

UTF-16 也是一种可变长度的 Unicode 字符编码类型,唯一的区别是变量是 2 字节的倍数(取决于字符,或更具体地说,取决于字符集,为 2 字节或 4 字节)。它最初是固定 2 字节的字符编码,但后来由于 2 字节不足以容纳所有字符而使其成为可变长度的。

代理对

在 UTF-16 编码方案中,代理对 (surrogate-pair) 是由一对 16 位(UTF-16)Unicode 编码字符组成的,它们用于表示单个字符。必须注意的是,代理对的大小是 32 位,而不是 16 位。代理对既用于表示单个字符,而代理对的单个值本身无法映射到 Unicode 中的单个字符。

通过使用代理对,UTF-16 编码有机会支持一百万个额外的字符。请记住,所有这些字符表示(它们的数字代码)已经由 Unicode 定义,UTF-16 只是表示它们。

从技术上讲,代理对的第一个值称为高位代理(high-surrogate),其范围是从 0xD800 到 0xDBFF,而代理对的第二个值称为低位代理(low-surrogate pair),其范围是从 0xDC00 到 0xDFFF。

代理对最常见的用法是引用非基本多语言平面(non-BMP)(Basic Multilingual Plane)字符时。因此,使用两个 BMP 字符的值来映射到一个非 BMP 字符,而这两个值实际上来自 BMP 范围,但映射到一个非 BMP 字符。

在 .NET 框架中,你可以使用以下函数来确定一个字符是低位代理还是高位代理:

System.Char.IsLowSurrogate(char); // character object
System.Char.IsHighSurrogate(char); // character object

.NET 框架的 `char` 对象支持检查代理(在代理对中)以及许多其他与 Unicode 数据相关的函数。

UTF-32

UTF-32 每个字符使用确切的 32 位(或 4 字节)。无论码点、字符集或语言如何,这种编码都会为每个字符使用 4 字节。UTF-32 唯一的好处(根据维基百科)是字符可以直接索引,这在可变长度的 UTF 编码中是不可能的。然而,我认为这种编码最大的缺点是每个字符 4 字节的大小,即使你只使用拉丁字符;或特别是 ASCII 字符。

Unicode

当你尝试使用 Unicode 提供的默认编码时,你实际上是在引用 UTF-16LE(使用 Little-Endian 字节表示的 16 位字符编码;或字节顺序)。因此,除了作为标准的名称之外,Unicode 还是一种引用 UTF-16LE 的编码类型(在 .NET 框架中阅读相关内容)。

另外,请记住,这个术语是微软在其 .NET 框架的 `Encoding` 类成员中创造的。这不是标准,而是编码类型;UTFs。这就是为什么它被映射到 UTF-16LE。在下面阅读更多关于 UTF-16LE 的内容

字节序

字节序(Endianness)是数字计算中字(word)的字节顺序。字节序有两种类型:

  1. 大端序(Big-Endian)
    使用大端序的机器首先将最高有效位存储在内存中。
  2. 小端序(Little-Endian)
    使用小端序的机器首先将最低有效位存储在内存中。

这取决于机器如何将字节存储在内存中。如果你的数据只有一字节,那么不用担心字节序,因为你每次读取的都是这一个字节。你也可以通过查看第一个字节并与其他字节进行比较来理解正在使用哪种字节序;大端序会先存储最大的字节,然后是比它小的字节,依此类推。

UTF-16BE

UTF-16BE 是一种将字符映射到其字节表示的编码,但字节表示与大端序相同;(BE 代表 Big-Endian),因此使用大端序的机器可以使用 UTF-16BE。

UTF-16LE

同样,UTF-16LE 是一种将字符编码为其字节表示的编码,该表示与小端序字节表示相同;也就是说,最低有效字节先出现,然后是越来越大的字节,依此类推。在 UTF-16LE 中,LE 代表 Little-Endian。

LE 和 BE 是完全相反的,前者使用最低有效字节作为第一个顺序,后者使用最高有效字节作为第一个顺序来排列块中的字节。因此,如果一个人使用字节顺序,他必须知道他正在使用哪种字节序来在内存中存储字节。当这些块要从内存中提取时,机器可以通过查看第一个字节来知道如何将字节编码回字符(将它们映射到一个字符);最低有效字节或最高有效字节

现在转向 .NET 框架

关于 Unicode 标准的背景知识已经够多了……现在我将继续介绍 .NET 框架及其对 Unicode 的支持。从基础的原始类型,如 char 开始,.NET 框架就支持 Unicode。在 .NET 框架中,一个 `char` 是 2 字节的,并支持字符的 Unicode 编码方案。通常,你可以指定使用任何 Unicode 编码来处理你的字符和字符串,但默认情况下,你可以认为它支持 UTF-16(2 字节)。

char (C# 参考) .NET 文档

Char 结构 (System)

以上文档包含不同内容,但都相似…… `char` 用于在 .NET 框架中声明 `System.Char` 对象的一个实例。默认情况下,.NET 框架也支持 Unicode 字符,并在屏幕上渲染它们,你甚至不需要编写任何单独的代码;只需确保数据源的编码正确即可。.NET 框架中的所有应用程序,如 WPF、WCF 和 ASP.NET 应用程序都支持 Unicode。你可以在所有这些应用程序中使用所有 Unicode 字符,.NET 会将代码渲染成其字符表示。请阅读接下来的部分

等等,你为什么没提到控制台应用程序?

这是一个需要注意的好问题,因为我提到了所有 .NET 应用程序都支持 Unicode,但没有提到控制台应用程序。嗯,问题通常不在于 Unicode 支持本身,也不是平台或控制台框架本身。这是因为控制台应用程序从来没有被设计成支持如此多的图形;是的,支持不同的字符就是图形,你应该阅读关于字形(glyphs)的内容。

当我开始在控制台应用程序中测试 Unicode 支持时,我惊讶地发现 Unicode 字符支持不仅取决于底层的框架或正在使用的库,还有一个其他因素是你应该在尝试使用 Unicode 支持之前考虑的。那就是你的控制台的字体系列;如果你打开控制台的属性,你会发现有多个字体。

现在,让我们尝试一些基本示例,从 0-127 的字符范围开始,然后到下一个代码页,看看控制台应用程序如何表现……以及其他应用程序可能如何响应我们的数据。

ASCII 码

首先,我将尝试在代码中测试 ASCII 码(一个非常基本的,'a')来查看控制台是否正常工作或出现问题。我使用了以下代码在控制台应用程序中执行:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleUnicode
{
    class Program
    {
        static void Main(string[] args)
        {
            // Characters a α क 
            char a = 'a';
            Console.WriteLine(String.Format("{0} character has code: {1}", a, 
                                                 ((int)'a').ToString());

            // Just for sake of pausing
            Console.Read();
        }
    }
}

上面的代码获取 'a' 字符的整数表示;这是一个获取字符值的简单方法。在之前的版本中,我曾经通过将其编码为 UTF-8 字节数组来获取值,但在大多数情况下这会给出错误的值,而这个方法则提供了准确的值。再次,你可能想知道为什么 'a' 在 UTF 中得到 61 的值?这是因为 U+0061 的表示形式是整数(十进制)值的十六进制表示,在本例中是 **97**。你可以自己将 97 转换为十六进制!关于转换的更多信息,请阅读“将十进制转换为 Unicode 表示(U+XXXX)”部分,我在其中进行了更详细的解释。

对上述代码的响应如下:

你可以看到,现在代码来自 ASCII 还是 Unicode 没有任何区别,因为 'a' 在两者中都是 97。这非常基础。现在,让我们更进一步……

非 ASCII 码

现在让我们尝试希腊字母,第一个字母,alpha……如果执行与上面类似的查找代码,并将 'a' 替换为 alpha,你将看到以下结果:

嗯,这些东西进行得很顺利。

现在让我们迈出一大步,为什么不试试印地语?印地语经常被问及,比如如何从数据库存储和提取印地语字母等等。现在让我们在控制台应用程序中尝试印地语字符。

不行——我没要问号!那个本应是印地语中的“k”发音字符……结果它不是,而是一个听起来像“q”的问号。为什么会这样?

这不是 Unicode 的问题,而是控制台应用程序对全局字体支持不足。为了回答这个问题,我创建了另一行代码,使用 Unicode 支持将此代码存储到 txt 文件中。下面是存储字符二进制(使用 UTF-8 编码)的代码。

注意,记事本可以支持 ASCII、Unicode 和其他方案,所以在保存数据之前,请确保你的文件支持字符集。

File.WriteAllBytes("F:\\file.txt", Encoding.UTF8.GetBytes(a.ToString()));

上面代码中相同的字符在控制台执行时打印了一个问号,但文件却显示了其他内容。

这表明,字符在 .NET 框架中得到了广泛支持,但字体也很重要,字体中的字形必须可用才能渲染字符,否则应用程序将只显示这些字符(在其他框架中,方框通常表示不支持的字符)。

结论

因此,根据这个假设,如果你的应用程序在控制台应用程序中显示 Unicode 字符时遇到任何问题,你需要确保你试图显示的字符在你使用的字体系列中得到支持。这个问题类似于在控制台应用程序中加载一个字体系列不支持的印地语字符。除非你更新字体系列以支持该代码页(或至少该码点),否则对控制台应用程序的 Unicode 字符支持的讨论将在此结束。

其他应用程序框架中的 Unicode 支持

现在让我们看看其他框架,如 WPF 和 ASP.NET,对 Unicode 的支持程度。我将不使用 Windows Forms;过程与 WPF 类似。ASP.NET 和 WPF 拥有各种字体和字形,可以支持不同的字符;几乎所有的字符。所以,让我们继续从软件框架到 Web 框架,最后测试 SQL Server 数据库,看看对每个框架的支持情况,以测试 Unicode 字符支持的实际情况……

首先定义数据源

在继续介绍任何框架之前,我想介绍一下本文将使用的数据源,以展示如何从多个数据源以 Unicode 格式读写数据。在本文中,我将使用:

  1. 记事本;它支持多种编码,ASCII、Unicode 等。
  2. SQL Server 数据库,用于在行和列中存储数据。

你可以使用这些数据源中的任何一个(如果你使用基于 Windows 的操作系统,第一个是可用的),它们都支持 Unicode 数据读写。如果你打算通过代码写入数据并创建文件,那么什么都不需要……否则,如果你打算自己创建新文件并命名,那么在保存之前,请确保在点击保存按钮创建文件之前选择了 UTF-8 编码(而不是 Unicode,即 UTF-16),否则它将是默认的 ASCII 编码,Unicode 数据将被丢失。你可以使用记事本作为数据源,或者如果你有 SQL Server,你可以使用 SQL Server 作为你的数据源;它们都可以满足你的需求。

使用 SQL Server 数据库

你也可以在你的项目中使用 SQL Server 数据库,如果你要使用这里提供的源代码,你可能需要创建一个示例数据库,并在新创建的数据库(或你当前的测试数据库中)创建一个新表,用于保存 Language 和 UnicodeData。你也可以运行以下 SQL 命令来完成此操作……

CREATE TABLE (
   Langauge nvarchar(50),
   UnicodeData nvarchar(500)
);

请确保你选择了正确的数据库来创建表,或者在使用此命令之前使用 `USE DATABASE_NAME` 命令执行。最初,我用以下数据填充了数据库。

Langauge     |     UnicodeData

Arabic       |     بِسْمِ اللهِ الرَّحْمٰنِ الرَّحِيْمِ
Hindi        |     यूनिकोड डेटा में हिंदी
Russian      |     рцы слово твердо
English      |     Love for all, hatred for none!
Urdu         |     یونیکوڈ ڈیٹا میں اردو

足够多的数据和语言可以再次测试我们的框架。我确定控制台永远不支持它,所以为什么还要尝试呢?尽管如此,如果你想在控制台应用程序中看到输出,我不会反对……

WPF 与 Unicode

控制台应用程序中唯一的问题是字体系列中对字符字形的有限支持,这在 WPF 中得到了克服。在 WPF 中,你可以使用相当多的字体(系统字体或你自己生成的自定义字体)来以你想要的方式显示应用程序中的不同字符。

WPF 支持所有字符,我们将看到为什么我这么说。首先,让我们用纯文本写一个简单的表达式,现在开始在 WPF 中打印相同的字符……完成这个之后,我将尝试看看字体是否是 WPF 的一个因素……请继续关注。

1. 'a' 字符

首先,我将尝试将 'a' 字符打印到屏幕上,并查看它的编码代码;这与 ASCII 类似。以下代码可以解释:

// text is a TextBlock control in WPF and String.Format can be used to format the string
text.Text = String.Format("character '{0}' has a code: {1}", "a", ((int)'a'));

Int32 可以映射到 Unicode 中的所有字符并存储它们的十进制值。

现在执行上述代码后,将打印以下输出。

输出与控制台应用程序非常相似。继续前进……

2. 'α' 字符

现在,转向那个希腊字符并尝试一下,会得到以下屏幕输出:

3. 'क' 字符

现在轮到那个有问题的字符了,印地语字符,将在我们的应用程序中进行测试,看看它对我们的应用程序有什么影响。当我们更改代码打印并用 `क` 填充时,我们得到:

这表明 WPF 确实支持该字符,因为字体系列;Segoe UI,支持 Unicode 字符。当前实例是印地语字母集。

在 WPF 中测试 SQL Server 数据

我们已经看到了控制台应用程序如何处理数据,现在是时候测试我们的 WPF 应用程序,看看它如何处理来自 SQL Server 的 Unicode 数据,并看看它是在屏幕上显示原始数据,还是我们需要对其进行处理。我将创建一个 SqlClient 并对我的数据库的 SqlConnection 运行一些 SqlCommands。

你需要一个 SQL Server 数据库的连接字符串。

// Create the connection.
using (SqlConnection conn = 
       new SqlConnection("server=your_server;database=db_name;Trusted_Connection=true;"))
{
    // DO REMEMBER TO OPEN THE CONNECTION!!!
    conn.Open();

    // Connection Established
    SqlCommand command = new SqlCommand("SELECT * FROM UnicodeData", conn);

    // For better readability
    text.FontSize = 13;

    // End the line for the data in the database
    text.Text = "Langauge\t | \tUnicodeData" + Environment.NewLine + Environment.NewLine;
    using (SqlDataReader reader = command.ExecuteReader())
    {
        while (reader.Read())
        {
            // Write the data
            text.Text += reader[0] + "\t \t | \t" + reader[1] + Environment.NewLine;
        }
    }
}

现在 WPF 在屏幕上显示以下输出……

上面的图片显示,我们不需要额外的工作来渲染数据,WPF 为我们完成了这项工作。

添加和检索数据

通常人们说他们以正确的格式存储了数据,但当他们尝试提取数据时,却得到了错误格式的数据。大多数情况下,印地语、阿拉伯语、乌尔都语和日语用户会提出这类问题,所以我认为我也应该概述一下当用户将数据存储到数据源时会发生什么。我使用了以下代码将 3 行不同的数据插入到数据库中:

SqlCommand insert = new SqlCommand(@"INSERT INTO UnicodeData (Language, UnicodeData) 
                                                                VALUES (@lang, @data)", conn);

var lang = "language in every case";
var udata = "a few characters in that particular language";

// Adding the parameters
insert.Parameters.Add(new SqlParameter("@lang", lang));
insert.Parameters.Add(new SqlParameter("@data", udata));

if (insert.ExecuteNonQuery() > 0)
{
    // Stop the app, to view this message!
    MessageBox.Show("Greek data was stored into database, now moving forward to load the data.");
}

我插入的数据是:

希腊语         |     Ελληνικών χαρακτήρων σε Unicode δεδομένων
中文       |     祝好运
日语     |     幸運

现在数据库表看起来应该是这样的:

字体在 WPF 中也重要吗?

在控制台应用程序中,字体系列也很重要,同样的问题是,“字体系列在 WPF 中也重要吗?”答案是,“是的!它很重要”。但实际的底层过程是不同的。WPF 框架会将字符映射到其编码,并将编码映射到每个字体系列的字符。如果它无法将字符映射到编码,它将回退到默认的字体系列,该字体系列支持该字符。

如果你阅读 MSDN 上关于 `System.Windows.Media.FontFamily` (FontFamily) 类的文档,你会找到一个名为“字体回退”(Font Fallback)的有趣部分,其中提到:

引用

字体回退是指客户端应用程序选择的字体以外的字体的自动替换。触发字体回退的两个主要原因是:

  • 客户端应用程序指定的字体在系统上不存在。

  • 客户端应用程序指定的字体不包含渲染文本所需的字形。

现在等等,还没完。这并不意味着 WPF 会使用自定义字体而不是该字体,或者会创建一个方框或问号。实际上发生的事情很精彩(在我看来),WPF 使用默认字体回退字体系列,从而为该编码提供一个默认的、非自定义的字体。你应该阅读该文档来理解 WPF 中的字体。总之,让我们更改 WPF 应用程序中的字体看看会发生什么。

添加以下代码将更改我的 `TextBlock` 的字体系列:

text.FontFamily = new FontFamily("Consolas");

现在屏幕上的输出如下。

我们看到的结果与之前的输出非常相似。这是因为,只有映射到 Consolas 字体系列的字符才会被渲染为 Consolas 字体,而其他字符(乌尔都语、阿拉伯语、印地语等)则通过字体回退机制映射回 Segoe UI 字体,从而使我们的应用程序能够在用户不看到问号和方框的情况下查看数据。WPF 的这个功能是我喜欢的功能之一。所有这些都在后台发生。

ASP.NET 对 Unicode 的支持

现在让我们看看 ASP.NET 是否也支持 Unicode 数据,或者我们是否需要对其进行一些工作来显示 Unicode 编码的字符。到目前为止,我一直说“ASP.NET 是运行在 .NET 框架之上的,所以任何在 .NET 框架上运行的东西,如果不能在前台使用,可以在 ASP.NET 的后台使用”。现在是时候测试 Unicode 支持了。

在 ASP.NET 中测试 SQL Server 数据

我将跳过在 WPF 示例中展示的三个字符,因为大家都知道,如果这些字符能在 HTML 中编写,它们就能在 ASP.NET 中解析。所以,我将直接转向来自 SQL Server 的数据。

我将使用 WPF 应用程序中的相同代码并在我的 ASP.NET 网站中进行测试,渲染后,它显示以下输出。

我知道段落的格式有点混乱,但我不想处理 HTML 渲染和 Web 文档的 CSS,所以我留给你来理解一切都在正常工作。在这种情况下,输出也与 WPF 的输出相似,并显示字符被正确渲染;此网页的字体系列也是 Segoe UI。

在 ASP.NET 中字体也重要吗?

这里的答案也相似,字体很重要……但还有一些其他因素会起作用。

  1. ASP.NET 从服务器端会以正确且格式良好的 HTML 形式发送响应;所有 Unicode 字符都嵌入在文档中。
  2. Web 浏览器将在支持字符集方面发挥重要作用。你必须依赖浏览器来支持发送的字符,与 WPF 不同,没有字体回退可以在客户端显示默认字体;大多数新浏览器都有其软件的自定义机制,并且行为与字体回退类似。
    (现在使用 `<meta charset="utf-8" />` 似乎有道理,不是吗?)
  3. 正在使用的操作系统,操作系统也应该支持 Unicode,你不能指望每个人都在运行最新版本的各种软件,并且拥有一台拥有最高评分操作系统的最佳机器。

检查这些条件,你可以假设 ASP.NET 在服务器端会尽一切努力来确保数据不丢失(数据丢失是指数据以方框形式或带有问号的菱形框等形式显示)。所以,在 ASP.NET 中,字体不仅重要,Web 浏览器和字符集也很重要……这使得开发者在预测会发生什么时有点不同。WPF 有 .NET 框架的版本在后台运行以执行回退,ASP.NET 没有这样的机会,因为使用 Windows 95(虽然很少见)的用户可以向你的 ASP.NET 5 网站发出 Web 请求。

将字体更改为 Consolas 在 ASP.NET 中并在我的 Windows 8.1 的 Google Chrome 浏览器中测试,我没有发现任何区别……

但是如果你注意到,字体没有回退,但它们之间有轻微的差异(查看阿拉伯语、俄语,有轻微变化),这表明 Web 浏览器本身对其有自定义支持,可以渲染这些字符。这一点很有趣。

附加数据和示例……

使用记事本的用户指南

如果你要使用记事本,你将不得不使用 `System.IO` 命名空间,在 `File` 类中,你可以使用 `ReadAllBytes` 来确保你使用的是正确的编码方案。例如,以下代码可用于读取文件中的所有字节。

byte[] bytes = File.ReadAllBytes("location");

然后,你可以使用 `System.Text` 命名空间中的 `Encoding` 类将这些字节写入屏幕,以将它们转换为 String。请看下面:

string data = Encoding.UTF8.GetString(bytes); // accepts byte[]

现在数据将包含Unicode 字符。然后你可以通过使用你自己的框架中的方法将其输出到屏幕上。你也可以将数据编码回并存储到文件中,你可以将字符串转换为字节数组并将所有字节存储到文件中……看下面:

string data = GetData(); // get the data from somewhere.
byte[] bytes = Encoding.UTF8.GetBytes(data);
File.WriteAllBytes("location", bytes);

现在文件(如果支持 Unicode 编码)将显示其中存储的 Unicode 字符。这个过程与使用 SQL Server 几乎相同,只是类和方法不同。你不能说哪一个比另一个更好。两者都是免费的,而且花费的时间不多。

将十进制转换为 Unicode 表示(U+XXXX)

很多人认为 `&#065;` 和 `U+0041` 这两个数据表示不同的字符;我曾经也是这么想的……也许是因为在十进制中 65 和 41 代表的是不同的字符。嗯,你错了,我也错了。它们表示同一个字符,'A'。在继续之前,你应该阅读这篇维基百科文章。它涵盖了所有基本字符和特殊字符。

现在,它们背后的基本思想是,字符的数字 65 是十进制数。而 41 是十六进制数据,表示同一个字符;有意义吗?它们只是不同基数下表示同一个数字的不同记法。你可以像转换一个数字到另一个基数一样,相互转换它们。在 .NET 框架中,获取字符的数字表示以及根据数字获取字符的代码是:

// From character to number
char a = 'A'; // you can use escaped characters too
int b = (int)a; // holds 65

// From number to character
int a = 65;
char b = (char)a; // holds 'A'

使用这个机制,你可以轻松地从字符获取整数值,并从整数值获取字符。现在,一旦你获得了十进制表示,你就可以用它来将其转换为该字符的 Unicode 表示。看下面的代码:

string str = String.Format("Character '{0}' has code: {1} and Unicode value: U+{2}",
                           'A', ((int)'A'), ((int)'A').ToString("X4"));

当上述代码执行时会发生什么?

上图胜过千言万语,不是吗?它消除了关于这些数字如何表示、实际值是多少等等的许多模糊之处。你可以根据自己的喜好以任何一种方式表示数字。

不要通过字节来假设你获得了字符的值

`File.ReadAllBytes` 在将数据保存在字节数组中有意义,但永远不要相信一个字符的值会保存在一个字节中。一个字节可以容纳 0-255 的数据,任何超过 255 的数据都会被映射为表示该字符的方式;因此是一个字节数组。如果你尝试获取该字节的值,你将得到错误的值,尽管渲染结果可能是正确的。例如,看下面这行代码。

这是**绝对错误的值**,而 **α ('α') 的正确值**是 **945**。

字节数组中的值是按顺序映射的,以保持正确的字符方案,而不是将每个字符的值放在其自己的索引处。如果你尝试获取数组中的第一个值,你将得到错误的值,并会认为你得到了正确的值!

为什么选择 UTF-8 而不是其他编码?

我提到了为什么选择 UTF-8 而不是其他编码方案。让我告诉你原因。主要因素是 UTF-8 编码的轻量级。它是由 Unicode consortium 提供的一种 8 位可变长度编码方案。它使用 1 字节处理 0-255 的字符,然后使用 2 字节处理下一个代码页,依此类推。而 UTF-32 是固定大小的,UTF-16 对每个字符至少使用 2 字节。

UTF-8,如果用于基于英语的网站,则映射到 ASCII 码,从而为只支持 ASCII 编码方案的旧机器提供支持。如果你去这个 MSDN 文档并阅读示例部分中的示例,你将发现 UTF-8 是所有 Unicode 标准中最好的版本。还有其他一些线程讨论了不同的……我可能会带你到那里了解更多。

  1. 维基百科:Unicode 编码比较
  2. Jon Skeet 在 Stack Overflow 上关于 UTF-8 与 Unicode 的回答
  3. UTF8、UTF16 和 UTF32(在 Stack Overflow 上)
  4. UTF-8 和 UTF-16 之间的区别(在 Stack Overflow 上)

System.Text 中提供了哪些类?

在 .NET 框架中,System.Text 命名空间负责所有基于字符的编码。它包括 `Encoding` 类,该类可用于将字符串编码为字节和/或将字节编码为字符串等。在 .NET 框架中,允许使用许多选项。

模糊之处在于,`Encoding` 类中有 `Unicode`、`UTF8`、`UTF7`、`UTF32` 等成员(请注意,没有 UTF16,Unicode 本身就是 UTF-16)。值得注意的是,也有用于这些编码的类。你可以同时使用它们,例如 `Encoding.UTF8`,或为你的应用程序使用 `UTF8Encoding`。关于 `System.Text` 的文档有更多资源供你阅读和理解 .NET 框架中的编码概念。

注意:`UTF8Encoding` 继承自 `Encoding`。或者更具体地说,`System.Text` 命名空间中以 `Encoding` 结尾的每个类都继承自 `Encoding` 类,并且功能与 `Encoding` 类成员相似。

关注点

Unicode

Unicode 是计算机字符编码和解码的标准。你可以使用 Unicode 的不同编码,如 UTF-8(8 位)、UTF-16(16 位)等。这些编码用于应用程序的全球化,并为用户提供本地化界面,使他们能够使用自己的语言(而不仅仅是英语)使用应用程序。在大多数框架(包括 .NET 和 Java)中,Unicode 指的是 UTF-16 编码;每个字符 2 字节大小。代理对用于表示无法直接映射到 2 字节 UTF-16 编码代码页的其余字符。

为什么使用 UTF-8?

UTF-8 是 Unicode 提供的一种可变长度编码,可以容纳所有字符,其大小从 1 字节到 4 字节不等,取决于字符所在的代码页。所有网站、新闻应用程序和媒体设备都使用 UTF-8,因为它轻量且高效。

Unicode 在 .NET 中

.NET 框架内置支持 Unicode 字符;.NET 框架中的 `char` 对象表示一个 Unicode 字符;**UTF-16**。

你可以在不同的 .NET 应用程序中使用 Unicode 字符,从控制台应用程序、WPF 应用程序一直到基于 ASP.NET 框架的 Web 应用程序。你可以将字符串编码和解码为字符和整数。在控制台应用程序中,你需要确保你将要使用的字体系列支持该字符和代码页,否则屏幕上可能会显示问号。

你不需要编写任何代码来支持 Unicode,它默认就在那里

字符映射问题

正如在这个问题中开始讨论的那样,大多数情况下捕获的 Unicode 数据在某个地方丢失了,字符不再映射到有效字符。因此,程序会尝试在屏幕上打印 '?'。问题可能的原因在(已附加的)问题中提到,你可以查看一下。

另外,Sergey Alexandrovich Kryukov 提出了如何应对这种情况,他说:

Sergey Alexandrovich Kryukov:(*忽略斜体文本;特定于问题*)

如果字体系列是问题,它会显示一个方框,而不是 '?'。而且波斯-阿拉伯语几乎在所有地方都支持。存在非 Unicode 编码。这可能是该死的 864 - 完全不合适。或者可能是由于 `varchar`。很简单:**如果你将 Unicode 字符串转码为不支持某些字符的编码,所有软件都会生成 '?'**。就是这么简单。所以答案是:只使用 Unicode。没有必要使用任何 UTF(除非流被使用,最好远离)。例如,`nvarchar` 通过 ADO.NET 自动映射到 .NET Unicode 字符串。

你可以在上面的文章中阅读关于 Unicode 和 UTF 的内容。

历史

帖子的第一个版本。

帖子的第二个版本:我修复了大多数问题;尤其是控制台应用程序代码块中的字符到整数转换。我添加了一些段落来阐明代码的目的,并修复了一些拼写错误,还格式化了段落以获得更好的可读性。

第三个版本:进行了许多积极而有益的更改(感谢 Sergey Alexandrovich Kryukov 的出色支持和指导)。将标题从“不同的 Unicode”(有歧义)更改为“深入了解 Unicode 编码(很明显)。添加了字节序的详细信息,Unicode 作为标准以及 Unicode 作为编码。

© . All rights reserved.