Encoding.UTF8.GetString没有考虑Preamble/BOM

Ron*_*ein 25 .net unicode byte-order-mark character-encoding

在.NET中,我正在尝试使用Encoding.UTF8.GetString方法,它接受一个字节数组并将其转换为string.

看起来这种方法忽略了BOM(字节顺序标记),它可能是UTF8字符串的合法二进制表示的一部分,并将其作为字符.

我知道我可以TextReader根据需要使用a 来消化BOM,但我认为GetString方法应该是某种使我们的代码更短的宏.

我错过了什么吗?这是故意的吗?

这是一个复制代码:

static void Main(string[] args)
{
    string s1 = "abc";
    byte[] abcWithBom;
    using (var ms = new MemoryStream())
    using (var sw = new StreamWriter(ms, new UTF8Encoding(true)))
    {
        sw.Write(s1);
        sw.Flush();
        abcWithBom = ms.ToArray();
        Console.WriteLine(FormatArray(abcWithBom)); // ef, bb, bf, 61, 62, 63
    }

    byte[] abcWithoutBom;
    using (var ms = new MemoryStream())
    using (var sw = new StreamWriter(ms, new UTF8Encoding(false)))
    {
        sw.Write(s1);
        sw.Flush();
        abcWithoutBom = ms.ToArray();
        Console.WriteLine(FormatArray(abcWithoutBom)); // 61, 62, 63
    }

    var restore1 = Encoding.UTF8.GetString(abcWithoutBom);
    Console.WriteLine(restore1.Length); // 3
    Console.WriteLine(restore1); // abc

    var restore2 = Encoding.UTF8.GetString(abcWithBom);
    Console.WriteLine(restore2.Length); // 4 (!)
    Console.WriteLine(restore2); // ?abc
}

private static string FormatArray(byte[] bytes1)
{
    return string.Join(", ", from b in bytes1 select b.ToString("x"));
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 23

看起来这种方法忽略了BOM(字节顺序标记),它可能是UTF8字符串的合法二进制表示的一部分,并将其作为字符.

看起来它根本不"忽略"它 - 它忠实地将它转换为BOM角色.毕竟,这就是它.

如果你想让你的代码忽略它转换的任何字符串中的BOM,那就由你来做...或者使用StreamReader.

请注意,如果您要么使用Encoding.GetBytes之后Encoding.GetString 使用StreamWriter之后StreamReader,这两种形式要么产生再吞或不生产BOM.只有当您使用StreamWriter(使用Encoding.GetPreamble)和直接Encoding.GetString调用混合时才会使用"额外"字符.

  • @RonKlein此外,您可以说`restore2 = restore2.TrimStart('\ uFEFF')`删除前导BOM字符.我也有一次想知道为什么`(新的UTF8Encoding(true)).GetBytes("abc")`和`(新的UTF8Encoding(false)).GetBytes("abc")`产生相同的输出,但你可能现在知道,`GetBytes`并不认为你在文件的开头,因此它永远不会使用`GetPreamble`.如果你使用`GetBytes`或`GetString`,你必须明确地`GetPreamble`,或者明确跳过前导码. (6认同)

Per*_*erg 8

根据Jon Skeet的回答(谢谢!),这就是我刚刚做到的:

var memoryStream = new MemoryStream(byteArray);
var s = new StreamReader(memoryStream).ReadToEnd();
Run Code Online (Sandbox Code Playgroud)

请注意,这可能只会可靠,如果有工作,你是从读字节数组的BOM.如果没有,您可能希望查看另一个StreamReader构造函数重载,该重载采用Encoding参数,以便您可以告诉它字节数组包含的内容.


For*_*ntz 5

对于那些不想使用流的人,我使用 Linq 找到了一个非常简单的解决方案:

public static string GetStringExcludeBOMPreamble(this Encoding encoding, byte[] bytes)
{
    var preamble = encoding.GetPreamble();
    if (preamble?.Length > 0 && bytes.Length >= preamble.Length && bytes.Take(preamble.Length).SequenceEqual(preamble))
    {
        return encoding.GetString(bytes, preamble.Length, bytes.Length - preamble.Length);
    }
    else
    {
        return encoding.GetString(bytes);
    }
}
Run Code Online (Sandbox Code Playgroud)