StreamWriter和UTF-8字节顺序标记

时间:2011-03-10 21:21:12

标签: c# file-encodings

我遇到了StreamWriter和Byte Order Marks的问题。文档似乎表明Encoding.UTF8编码已启用字节顺序标记,但在写入文件时,某些文件具有标记而其他文件没有。

我正在以下列方式创建流编写器:

this.Writer = new StreamWriter(this.Stream, System.Text.Encoding.UTF8);

任何有关可能发生的事情的想法都将受到赞赏。

9 个答案:

答案 0 :(得分:79)

正如某人已经指出的那样,没有编码参数的调用就可以了。 但是,如果您想要明确,请尝试以下方法:

using (var sw = new StreamWriter(this.Stream, new UTF8Encoding(false)))

关键是构造一个新的UTF8Encoding(false),而不是使用Encoding.UTF8Encoding。这是为了控制是否应该添加BOM。

这与没有编码参数调用StreamWriter相同,在内部它只是做同样的事情。

答案 1 :(得分:16)

问题是由于您使用UTF8 property上的静态Encoding class

Encoding属性返回的UTF8类的实例上调用GetPreamble method时,它返回字节顺序标记(三个字符的字节数组)并写入在将任何其他内容写入流之前的流(假设是新流)。

您可以通过自己创建UTF8Encoding class的实例来避免这种情况,如下所示:

// As before.
this.Writer = new StreamWriter(this.Stream, 
    // Create yourself, passing false will prevent the BOM from being written.
    new System.Text.UTF8Encoding());

根据default parameterless constructor(强调我的)的文档:

  

此构造函数创建一个不提供Unicode字节顺序标记的实例,并且在检测到无效编码时不会引发异常。

这意味着对GetPreamble的调用将返回一个空数组,因此不会将BOM写入基础流。

答案 2 :(得分:12)

我唯一一次看到构造函数没有添加UTF-8 BOM就是当你调用它时流不在位置0。例如,在下面的代码中,不写入BOM:

using (var s = File.Create("test2.txt"))
{
    s.WriteByte(32);
    using (var sw = new StreamWriter(s, Encoding.UTF8))
    {
        sw.WriteLine("hello, world");
    }
}

正如其他人所说,如果您使用StreamWriter(stream)构造函数而未指定编码,那么您将看不到BOM。

答案 3 :(得分:12)

我的回答是基于HelloSam的,其中包含所有必要的信息。 只有我相信OP要求的是如何确保将BOM发送到文件中。

因此,您需要传递true,而不是将false传递给UTF8Encoding ctor。

    using (var sw = new StreamWriter("text.txt", new UTF8Encoding(true)))

尝试下面的代码,在十六进制编辑器中打开生成的文件,看看哪一个包含BOM,哪些不包含。

class Program
{
    static void Main(string[] args)
    {
        const string nobomtxt = "nobom.txt";
        File.Delete(nobomtxt);

        using (Stream stream = File.OpenWrite(nobomtxt))
        using (var writer = new StreamWriter(stream, new UTF8Encoding(false)))
        {
            writer.WriteLine("HelloПривет");
        }

        const string bomtxt = "bom.txt";
        File.Delete(bomtxt);

        using (Stream stream = File.OpenWrite(bomtxt))
        using (var writer = new StreamWriter(stream, new UTF8Encoding(true)))
        {
            writer.WriteLine("HelloПривет");
        }
    }

答案 4 :(得分:5)

您是否为每个文件使用相同的StreamWriter构造函数?因为文档说:

  

要使用UTF-8编码和BOM创建StreamWriter,请考虑使用指定编码的构造函数,例如StreamWriter(String,Boolean,Encoding)。

前一段时间我处于类似情况。在编写Encoding.GetPreamble()

之前,我最终使用了Stream.Write方法而不是StreamWriter并编写了Encoding.GetBytes(stringToWrite)的结果

答案 5 :(得分:3)

我发现这个答案很有用(感谢@Philipp Grathwohl和@Nik),但在我的情况下,我正在使用FileStream来完成任务,因此,生成BOM的代码如下:

using (FileStream vStream = File.Create(pfilePath))
{
    // Creates the UTF-8 encoding with parameter "encoderShouldEmitUTF8Identifier" set to true
    Encoding vUTF8Encoding = new UTF8Encoding(true);
    // Gets the preamble in order to attach the BOM
    var vPreambleByte = vUTF8Encoding.GetPreamble();

    // Writes the preamble first
    vStream.Write(vPreambleByte, 0, vPreambleByte.Length);

    // Gets the bytes from text
    byte[] vByteData = vUTF8Encoding.GetBytes(pTextToSaveToFile);
    vStream.Write(vByteData, 0, vByteData.Length);
    vStream.Close();
}

答案 6 :(得分:2)

似乎如果文件已经存在且不包含BOM,那么在覆盖时它将不包含BOM,换句话说,StreamWriter在覆盖文件时会保留BOM(或者它不存在)。

答案 7 :(得分:0)

请你说明它不生产它的情况?唯一没有出现前导码的情况是,当没有任何内容写入作者时(Jim Mischel似乎找到了另一个,合乎逻辑的,更可能是你的问题,看到它的答案)。

我的测试代码:

var stream = new MemoryStream();
using(var writer = new StreamWriter(stream, System.Text.Encoding.UTF8))
{
    writer.Write('a');
}
Console.WriteLine(stream.ToArray()
    .Select(b => b.ToString("X2"))
    .Aggregate((i, a) => i + " " + a)
    );

答案 8 :(得分:0)

阅读SteamWriter的源代码后,您需要确定您正在创建一个新文件,然后字节顺序标记会添加到文件中。
https://github.com/dotnet/runtime/blob/6ef4b2e7aba70c514d85c2b43eac1616216bea55/src/libraries/System.Private.CoreLib/src/System/IO/StreamWriter.cs#L267
Flush方法中的代码

<块引用>

if (!_haveWrittenPreamble)
{
_haveWrittenPreamble = true;
ReadOnlySpan 前导码 = _encoding.Preamble;
if (preamble.Length > 0)
{
_stream.Write(前导);
}
}

https://github.com/dotnet/runtime/blob/6ef4b2e7aba70c514d85c2b43eac1616216bea55/src/libraries/System.Private.CoreLib/src/System/IO/StreamWriter.cs#L129
代码设置_haveWrittenPreamble

的值 <块引用>

// 如果我们要追加到一个已经有数据的流,不要 写
// 序言。
如果 (_stream.CanSeek && _stream.Position > 0)
{
_haveWrittenPreamble = true;
}