可以解析utf-8和Latin-1之间可打印字符的编码差异吗?

问题描述:

我读到可打印字符的Latin-1和UTF-8之间应该没有区别。我认为拉丁-1 'Ä'会映射两次到utf-8。 一次到多字节版本,一次直接。可以解析utf-8和Latin-1之间可打印字符的编码差异吗?

为什么看起来情况并非如此?

它看起来似乎标准可能包括任何看起来像延续字节的东西,但不是作为latin-1内含义的延续,而不会丢失任何东西。

我是否错过了一个标志或者是让我像描述的那样转换数据的东西,还是我错过了更大的图片?

这里是一个C#示例:

我的系统上的输出是

enter image description here

static void Main(string[] args) 
    { 
     DecodeTest("ascii7", " ~", new byte[] { 0x20, 0x7E }); 
     DecodeTest("Latin-1", "Ä", new byte[] { 0xC4 }); 
     DecodeTest("UTF-8", "Ä", new byte[] { 0xc3, 0x84 }); 
    } 

    private static void DecodeTest(string testname, string expected, byte[] encoded) 
    { 
     var utf8 = Encoding.UTF8; 
     string ascii7_actual = utf8.GetString(encoded, 0, encoded.Length); 
     //Console_Write(encoded); 
     AssertEqual(testname, expected, ascii7_actual); 
    } 

    private static void AssertEqual(string testname, string expected, string actual) 
    { 
     Console.WriteLine("Test: " + testname); 
     if (actual != expected) 
     { 
      Console.WriteLine("\tFail"); 
      Console.WriteLine("\tExpected: '" + expected + "' but was '" + actual + "'"); 
     } 
     else 
     { 
      Console.WriteLine("\tPass"); 
     } 
    } 

    private static void Console_Write(byte[] ascii7_encoded) 
    { 
     bool more = false; 
     foreach (byte b in ascii7_encoded) 
     { 
      if (more) 
      { 
       Console.Write(", "); 
      } 
      Console.Write("0x{0:X}", b); 
      more = true; 
     } 
    } 
+1

有一个没有 “直接” 的版本。所有127以上的代码点都使用UTF-8编码2或更多字节。由于'Ä'在ASCII范围之外,显然它至少会有2个字节。可以有不同版本的是你可以用[组合形式](https://en.wikipedia.org/wiki/Unicode_equivalence)或[预先合成的形式](https://en.wikipedia.org/wiki/Precomposed_character )取决于你选择的规范化形式 –

+1

无论你读什么都是无稽之谈,你发现它是。只有在使用正确的编码时,才会将0xC4转换为'Ä'。有*许多* 8位编码,但使用代码页1252或Encoding.Default往往是一个名为Johannes的人的正确选择。 –

+0

LưuVĩnhPhúc:字符串由一个或两个字节(不是2个或更多字节)组成。 ASCII字符(0到255)将MSB设置为零。字符串和字符是具有私有属性的两个字节的对象,指示字符是一个还是两个字节。 Encoding类可以将私有属性正确设置为一个或两个字节。 – jdweng

我看应该有没有区别的Latin-1和UTF-8可打印的字符。

您看错了。对于US-ASCII范围内的字符(U + 0000到U + 007F),Latin-1(包括ISO 8859系列其余部分在内的许多其他编码)和UTF-8之间没有区别。他们是不同的所有其他字符。

我以为latin-1'Ä'会映射两次到utf-8。一次到多字节版本,一次直接。

为了使这成为可能将需要UTF-8是有状态的,或以其它方式使用的信息前面的流中知道是否要解释一个八位位组作为多字节编码的直接映射或一部分。 UTF-8的一大优势在于它不是有状态的。

为什么它似乎像这样是不是这样的?

,因为它只是简单的错误。

这当然好像标准可以包括任何看起来像一个延续字节,但不是一个延续的没有失去任何东西的Latin-1中的含义。

它不能这样做,而不失去不是有状态的质量,这将意味着腐败会破坏错误后的整个文本,而不仅仅是一个字符。

我是否错过了一个标志或者是能够让我像所描述的那样转换数据的东西,还是我错过了更大的图片?

不,您对UTF-8和/或Latin-1的工作原理完全不正确。

一个标志,将消除是非状态和自同步UTF-8的简单性(你总是可以立即告诉你,如果是在一个单字节的字符,一个字符或部分的方式开始进入一个字符)正如刚才提到的。它也会消除UTF-8在算法上的简单性。所有UTF-8编码如下所示。

从码点映射到编码:

  1. 考虑字符的xxxx…例如位对于U+0027他们是100111对于U+1F308他们是11111001100001000

  2. 找到最小的下面,他们将适用于:

    0xxxxxxx

    110xxxxx 10xxxxxx

    1110xxxx 10xxxxxx 10xxxxxx

    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

所以U+0027001001110x27U+1F30811110000 10011111 10001100 100010000xF0 0x9F 0x8C 0x88

要从八位组转到代码点,请将其撤销。

要映射到拉丁文1,您只需将字符放入八位字节(显然只有在U + 0000至U + 00FF的范围内才有效)。

正如你所看到的,在U+0000U+007F之间的字符范围以外的字符不能在UTF-8和Latin-1中使用匹配的编码。 (“拉丁语1”也是CP-1252的名称,它是一种Microsoft编码,可以提供更多的可打印字符,但仍然只是UTF-8覆盖的一小部分)。

有一种方法,一个角色在理论上可以拥有多个UTF-8编码,但它被明确禁止。考虑到不是将U+0027的位放入单个单元00100111中,我们也可以将零填充并将其编码为11000000 10100111,将其编码为0xC0 0xA7。相同的解码算法会使我们回到U+0027(试试看)。然而,在引入这种同义词编码时不必要的复杂性也引入了安全问题,而且确实存在由接受过长的UTF-8的代码引起的真实世界的安全漏洞。

也许你需要扫描功能来决定哪个解码器是必需的?

试试这个:

/// <summary> 
/// Count valid UTF8-Bytes 
/// </summary> 
/// <returns> 
/// -1 = invalid UTF8-Bytes (may Latin1) 
/// 0 = ASCII only 7-Bit 
/// n = Count of UTF8-Bytes 
/// </returns> 
public static int Utf8CodedCharCounter(byte[] value) // result: 
{ 
    int utf8Count = 0; 
    for (int i = 0; i < value.Length; i++) 
    { 
    byte c = value[i]; 
    if ((c & 0x80) == 0) continue; // valid 7 Bit-ASCII -> skip 

    if ((c & 0xc0) == 0x80) return -1; // wrong UTF8-Char 

    // 2-Byte UTF8 
    i++; if (i >= value.Length || (value[i] & 0xc0) != 0x80) return -1; // wrong UTF8-Char 
    if ((c & 0xe0) == 0xc0) { utf8Count++; continue; } 

    // 3-Byte UTF8 
    i++; if (i >= value.Length || (value[i] & 0xc0) != 0x80) return -1; // wrong UTF8-Char 
    if ((c & 0xf0) == 0xe0) { utf8Count++; continue; } 

    // 4-Byte UTF8 
    i++; if (i >= value.Length || (value[i] & 0xc0) != 0x80) return -1; // wrong UTF8-Char 
    if ((c & 0xf8) == 0xf0) { utf8Count++; continue; } 

    return -1; // invalid UTF8-Length 
    } 
    return utf8Count; 
} 

和更新代码:

private static void DecodeTest(string testname, string expected, byte[] encoded) 
{ 
    var decoder = Utf8CodedCharCounter(encoded) >= 0 ? Encoding.UTF8 : Encoding.Default; 
    string ascii7_actual = decoder.GetString(encoded, 0, encoded.Length); 
    //Console_Write(encoded); 
    AssertEqual(testname, expected, ascii7_actual); 
} 

结果:

Test: ascii7 
     Pass 
Test: Latin-1 
     Pass 
Test: UTF-8 
     Pass