【问题标题】:How to convert a UTF-8 string into Unicode?如何将 UTF-8 字符串转换为 Unicode?
【发布时间】:2012-07-02 21:16:17
【问题描述】:

我有显示 UTF-8 编码字符的字符串,我想将其转换回 Unicode。

目前,我的实现如下:

public static string DecodeFromUtf8(this string utf8String)
{
    // read the string as UTF-8 bytes.
    byte[] encodedBytes = Encoding.UTF8.GetBytes(utf8String);

    // convert them into unicode bytes.
    byte[] unicodeBytes = Encoding.Convert(Encoding.UTF8, Encoding.Unicode, encodedBytes);

    // builds the converted string.
    return Encoding.Unicode.GetString(encodedBytes);
}

我在玩"déjà" 这个词。我已经通过这个online tool把它转换成UTF-8,所以我开始用字符串"déjÃ"测试我的方法。

不幸的是,通过这个实现,字符串保持不变。

我哪里错了?

【问题讨论】:

  • 这不是 UTF8 字符串。那是一个损坏的字符串,它是使用错误编码从字节中严重转换而来的。
  • UTF-8 Unicode。
  • C# 字符串有 16 位字符,因此它们不可能是 UTF-8 编码的。我认为系统无法理解您要执行的操作。你从哪里得到错误编码的字符串?
  • @AlexeyFrunze 和richard:如果有帮助,请在问题中为“Unicode”阅读“UTF-16”。 C#的原生字符串编码是UTF-16,在文档中称为Unicode。

标签: c# string unicode utf-8


【解决方案1】:

所以问题在于 UTF-8 代码单元值已作为 16 位代码单元序列存储在 C# string 中。您只需验证每个代码单元是否在一个字节范围内,将这些值复制到字节中,然后将新的 UTF-8 字节序列转换为 UTF-16。

public static string DecodeFromUtf8(this string utf8String)
{
    // copy the string as UTF-8 bytes.
    byte[] utf8Bytes = new byte[utf8String.Length];
    for (int i=0;i<utf8String.Length;++i) {
        //Debug.Assert( 0 <= utf8String[i] && utf8String[i] <= 255, "the char must be in byte's range");
        utf8Bytes[i] = (byte)utf8String[i];
    }

    return Encoding.UTF8.GetString(utf8Bytes,0,utf8Bytes.Length);
}

DecodeFromUtf8("d\u00C3\u00A9j\u00C3\u00A0"); // déjà

这很容易,但是最好找到根本原因;有人将 UTF-8 代码单元复制到 16 位代码单元的位置。可能的罪魁祸首是有人使用错误的编码将字节转换为 C# string。例如。 Encoding.Default.GetString(utf8Bytes, 0, utf8Bytes.Length).


或者,如果您确定您知道用于生成字符串的错误编码,并且错误的编码转换是无损的(通常情况下,错误的编码是单字节编码),那么您可以简单地做逆编码步骤获取原始 UTF-8 数据,然后您可以从 UTF-8 字节进行正确转换:

public static string UndoEncodingMistake(string mangledString, Encoding mistake, Encoding correction)
{
    // the inverse of `mistake.GetString(originalBytes);`
    byte[] originalBytes = mistake.GetBytes(mangledString);
    return correction.GetString(originalBytes);
}

UndoEncodingMistake("d\u00C3\u00A9j\u00C3\u00A0", Encoding(1252), Encoding.UTF8);

【讨论】:

  • 感谢 barnes53 这完全回答了我的问题,因为它产生了我期望的结果。你可以从我令人困惑的问题中找出我的意思。
【解决方案2】:

我有显示 UTF-8 编码字符的字符串

.NET 中没有这样的东西。 string 类只能以 UTF-16 编码存储字符串。 UTF-8 编码的字符串只能作为 byte[] 存在。尝试将字节存储到字符串中不会有好的结果; UTF-8 使用没有有效 Unicode 代码点的字节值。当字符串被规范化时,内容将被销毁。因此,在您的 DecodeFromUtf8() 开始运行时恢复字符串已经为时已晚。

仅处理带有 byte[] 的 UTF-8 编码文本。并使用 UTF8Encoding.GetString() 进行转换。

【讨论】:

  • 您指出了我想避免的混淆。我的字符串是 unicode 字符串,也是 .Net 字符串,调试器显示为 déjÃ。因此,我的目标是获取另一个(.Net)字符串,该字符串将显示为déjà(例如在调试器中)。
  • 您错过了答案的重点,没有办法使 每个 可能的 utf-8 编码字符串都能正常工作。你可以让它为 déjé 工作只是巧合。您已经遇到问题应该是一个提示,在最后一个 Ã 之后有一个额外的空格。一个特殊的,一个不间断的空格,代码点 U+00a0。这恰好是一个有效的 Unicode 代码点。
  • 谢谢,我想我明白了。你的意思是我不能使用string 来存储UTF-8 字节。但是,正如您提到的那样,它可能会意外工作,如果我能让意外发生,那将是一个很大的帮助。换句话说,我仍然不知道如何在它可以工作的情况下进行这种转换。
  • 您可以尝试使用 Encoding.Default.GetBytes() 来尝试恢复字节 []。我强烈推荐 System.Random 类,它的结果更可预测。
  • 我终于找到了(似乎)可以工作的东西。首先,我从这个臭名昭著的 UTF-8 字符串中得到一个byte[]。在这个数组中,我注意到所有奇数索引都包含0,所以我删除了所有这些索引并在这个结果上调用unicodeBytes = Encoding.Convert(Encoding.UTF8, Encoding.Unicode, encodedBytes);。最后,我返回了Encoding.Unicode.GetString(unicodeBytes);。然后,我挑选了许多语言的文本样本(感谢维基百科),构建了一个大字符串,将其转换为我臭名昭著的 UTF-8 格式,然后对其进行解码并得到完全相同的原始字符串。没有随机,没有意外。
【解决方案3】:

如果您有一个 UTF-8 字符串,其中每个字节都是正确的('Ö' -> [195, 0] , [150, 0]),您可以使用以下内容:

public static string Utf8ToUtf16(string utf8String)
{
    /***************************************************************
     * Every .NET string will store text with the UTF-16 encoding, *
     * known as Encoding.Unicode. Other encodings may exist as     *
     * Byte-Array or incorrectly stored with the UTF-16 encoding.  *
     *                                                             *
     * UTF-8 = 1 bytes per char                                    *
     *    ["100" for the ansi 'd']                                 *
     *    ["206" and "186" for the russian '?']                    *
     *                                                             *
     * UTF-16 = 2 bytes per char                                   *
     *    ["100, 0" for the ansi 'd']                              *
     *    ["186, 3" for the russian '?']                           *
     *                                                             *
     * UTF-8 inside UTF-16                                         *
     *    ["100, 0" for the ansi 'd']                              *
     *    ["206, 0" and "186, 0" for the russian '?']              *
     *                                                             *
     * First we need to get the UTF-8 Byte-Array and remove all    *
     * 0 byte (binary 0) while doing so.                           *
     *                                                             *
     * Binary 0 means end of string on UTF-8 encoding while on     *
     * UTF-16 one binary 0 does not end the string. Only if there  *
     * are 2 binary 0, than the UTF-16 encoding will end the       *
     * string. Because of .NET we don't have to handle this.       *
     *                                                             *
     * After removing binary 0 and receiving the Byte-Array, we    *
     * can use the UTF-8 encoding to string method now to get a    *
     * UTF-16 string.                                              *
     *                                                             *
     ***************************************************************/

    // Get UTF-8 bytes and remove binary 0 bytes (filler)
    List<byte> utf8Bytes = new List<byte>(utf8String.Length);
    foreach (byte utf8Byte in utf8String)
    {
        // Remove binary 0 bytes (filler)
        if (utf8Byte > 0) {
            utf8Bytes.Add(utf8Byte);
        }
    }

    // Convert UTF-8 bytes to UTF-16 string
    return Encoding.UTF8.GetString(utf8Bytes.ToArray());
}

在我的例子中,DLL 结果也是一个 UTF-8 字符串,但不幸的是,UTF-8 字符串是用 UTF-16 编码('Ö' -> [195, 0], [19, 32])解释的。 因此 ANSI '-' 是 150 被转换为 UTF-16 '-' 是 8211。如果你也有这种情况,你可以使用以下代替:

public static string Utf8ToUtf16(string utf8String)
{
    // Get UTF-8 bytes by reading each byte with ANSI encoding
    byte[] utf8Bytes = Encoding.Default.GetBytes(utf8String);

    // Convert UTF-8 bytes to UTF-16 bytes
    byte[] utf16Bytes = Encoding.Convert(Encoding.UTF8, Encoding.Unicode, utf8Bytes);

    // Return UTF-16 bytes as UTF-16 string
    return Encoding.Unicode.GetString(utf16Bytes);
}

或者原生方法:

[DllImport("kernel32.dll")]
private static extern Int32 MultiByteToWideChar(UInt32 CodePage, UInt32 dwFlags, [MarshalAs(UnmanagedType.LPStr)] String lpMultiByteStr, Int32 cbMultiByte, [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpWideCharStr, Int32 cchWideChar);

public static string Utf8ToUtf16(string utf8String)
{
    Int32 iNewDataLen = MultiByteToWideChar(Convert.ToUInt32(Encoding.UTF8.CodePage), 0, utf8String, -1, null, 0);
    if (iNewDataLen > 1)
    {
        StringBuilder utf16String = new StringBuilder(iNewDataLen);
        MultiByteToWideChar(Convert.ToUInt32(Encoding.UTF8.CodePage), 0, utf8String, -1, utf16String, utf16String.Capacity);

        return utf16String.ToString();
    }
    else
    {
        return String.Empty;
    }
}

如果您需要它,请参阅Utf16ToUtf8。 希望我能有所帮助。

【讨论】:

  • 只是为了确定:转换后的字符串仍然是UTF-16,它只是包含UTF-8编码数据。您无法使用 UTF-8 编码处理字符串,因为 .NET 将始终使用 UTF-16 编码来处理字符串。
【解决方案4】:

您所拥有的似乎是 string 从另一个编码错误解码,可能是 code page 1252,这是美国 Windows 的默认设置。假设没有其他损失,这是如何逆转的。一个不立即明显的损失是未显示的字符串末尾的non-breaking space (U+00A0)。当然最好一开始就正确读取数据源,但可能一开始就存储不正确。

using System;
using System.Text;

class Program
{
    static void Main(string[] args)
    {
        string junk = "déjÃ\xa0";  // Bad Unicode string

        // Turn string back to bytes using the original, incorrect encoding.
        byte[] bytes = Encoding.GetEncoding(1252).GetBytes(junk);

        // Use the correct encoding this time to convert back to a string.
        string good = Encoding.UTF8.GetString(bytes);
        Console.WriteLine(good);
    }
}

结果:

déjà

【讨论】:

    猜你喜欢
    • 2010-09-21
    • 2021-07-06
    • 1970-01-01
    • 2016-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-22
    • 2013-03-02
    相关资源
    最近更新 更多