关于c#:为什么不“Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(x))== x`

Why isn't `Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(x))==x`

在.NET中,为什么:

1
Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(x))

返回任意字节数组x的原始字节数组?

在回答另一个问题时提到了这个问题,但回答者没有解释原因。


首先,正如Watbywbarif所提到的,您不应该使用==来比较序列,这不起作用。

但是,即使您正确地比较了数组(例如,使用SequenceEquals()或仅仅通过查看它们),它们也不总是相同的。如果x是一个无效的utf-8编码字符串,就会出现这种情况。

例如,0xFF的1字节序列不是有效的utf-8。那么,Encoding.UTF8.GetString(new byte[] { 0xFF })返回什么?是吗?,u+fffd,替换字符。当然,如果你打电话给Encoding.UTF8.GetBytes(),它不会把0xFF还给你。


从另一个角度来看,Encoding类是为往返数据而设计的,但它们设计的往返数据是char数据,编码到byte中,而不是反过来。这意味着,在所讨论的Encoding的能力范围内,每个char值在byte值(1或更多)中都有相应的编码,这些值将恢复为完全相同的char值。(值得注意的是,并非所有的Encodings都可以对所有可能的char值进行此操作——例如,Encoding.ASCII只能支持[0, 128)范围内的char值。)

因此,如果您从字符数据开始,并且需要一种方法来存储或发送它到与字节一起工作的介质中(如磁盘上的文件或网络流),那么Encoding是将char数据转换为byte数据,然后在另一端重新转换的一种很好的方法。(如果要支持所有可能的字符串,则需要使用一个基于Unicode的Encodings,例如Encoding.UnicodeEncoding.UTF8。)

那么,如果你从一堆byte开始,这意味着什么?好吧,根据所讨论的编码,您正在使用的byte可能实际上不是Encoding可能输出的序列。您需要将Encoding.GetBytes视为一种编码操作,将Encoding.GetChars/Encoding.GetString视为一种解码操作,因此您要从任意字节数组开始并尝试对它们进行解码。

作为类比,考虑图像的jpeg文件格式。这是一种类似的编码和解码类型,在这种情况下,解码的数据不是string,而是图像。因此,如果您采用任意的字节字符串,那么将其解码为jpeg图像的可能性有多大?显然,这个问题的答案是非常微弱的。更可能的是,您的字节最终会进入解码器中的一条路径,该路径会说,"哇,我没想到该字节会出现在另一个字节之后",并且它将尽最大努力处理数据,前提是它是一个有效的jpeg文件,但不知何故被损坏。

当您将任意字节数组转换为字符串时,会发生完全相同的事情。UTF-8编码有关于char值128和up如何被编码的特定规则,其中一个规则说,在匹配像110xxxxx1110xxxx11110xxx这样的模式之后,您将只会看到一个与位模式10xxxxxx匹配的字节,它"引入"一个多字节序列(多个byte。代表单个char。因此,如果您的数据包含一个与模式10xxxxxx匹配的字节,而该模式不符合预期的"引导器",编码器只能假设数据以某种方式损坏。它是做什么的?它插入一个字符,上面写着,"编码的数据出了严重的问题。我尽力了。这就是它出错的地方。"设计Unicode的人预见到了这个精确的场景,并创建了一个具有这个精确含义的字符:替换字符。

因此,如果您试图在一系列char中往返于byte中,并且遇到这种情况,则会丢失有问题的byte的实际值,而插入替换字符。当您试图将string转换回byte数组时,它最终会编码替换字符,而不是原始数据。原始数据丢失。

您要寻找的是另一个方向的编码和解码关系。Encoding用于获取char数据,并找到一种临时存储为byte数据的方法。如果您想获取byte数据,并找到一种方法将其临时存储为char数据,则需要为此目的而设计的编码。幸运的是,这些存在。维基百科有一个相当全面的选项列表。-)

在.NET框架中,最简单和最容易访问的选项是mime base-64编码,它通过Convert.ToBase64StringConvert.FromBase64String公开。


这是因为==不会比较数组的每个元素。它与encoding.utf8没有连接。检查一下:

1
2
3
var a = new byte[] { 1 };
var b = new byte[] { 1 };
bool res = a == b;

字符编码(特别是utf8)对于同一代码点可能具有不同的形式。

因此,当您转换为字符串并返回时,实际的字节可能表示不同的(规范的)形式。

另见String.Normalize(NormalizationForm.System.Text.NormalizationForm.FormD)

参见:

  • 我能从unicode字符串中得到一个规范的utf-8字符串吗?
  • .NET的string.normalize是做什么的?
  • 规范化窗体

Some Unicode sequences are considered equivalent because they represent the same character. For example, the following are considered equivalent because any of these can be used to represent"?":

1
2
3
"\u1EAF"
"\u0103\u0301"
"\u0061\u0306\u0301"

However, ordinal, that is, binary, comparisons consider these sequences different because they contain different Unicode code values. Before performing ordinal comparisons, applications must normalize these strings to decompose them into their basic components.

该页面附带了一个很好的示例,向您展示了什么编码总是规范化的。