【问题标题】:StreamWriter and UTF-8 Byte Order MarksStreamWriter 和 UTF-8 字节顺序标记
【发布时间】:2011-07-13 01:42:40
【问题描述】:

我遇到了 StreamWriter 和字节顺序标记的问题。该文档似乎声明 Encoding.UTF8 编码启用了字节顺序标记,但是在写入文件时,有些有标记,而另一些则没有。

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

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

任何关于可能发生的事情的想法都将不胜感激。

【问题讨论】:

  • 请注意,虽然技术上允许在 UTF-8 中使用,但 Unicode 既不要求也不推荐 BOM(参见 ref)。一方面,它是无用的(与 UTF-16 不同)——UTF-8 字节顺序是由标准指定的。另一方面,它可能会破坏文本处理。例如,如果在 XML 序言之前有任何字符,许多 XML 解析器会阻塞。
  • 您确定要指定 UTF8 吗?因为如果你不指定它,它仍然会写一个UTF8,但是没有BOM
  • 来自 Unicode 标准 5.0:Unicode 标准还指定使用初始字节顺序标记 (BOM) 来明确区分某些 Unicode 编码方案中的大端或小端数据.
  • 您解决了这个问题吗?如果是这样,请标记正确答案或发布您自己的答案以帮助他人。

标签: c# file-encodings


【解决方案1】:

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

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

前段时间我也遇到过类似的情况。我最终使用了Stream.Write 方法而不是StreamWriter,并在编写Encoding.GetBytes(stringToWrite) 之前编写了Encoding.GetPreamble() 的结果

【讨论】:

    【解决方案2】:

    我唯一一次看到构造函数不添加 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。

    【讨论】:

    • 我认为“位置 0”基本上是关于这个问题的关键信息。
    • 另外,this 构造函数也不会输出 BOM:new StreamWriter("file.txt", Encoding.UTF8)
    【解决方案3】:

    能否请您展示它不生产它的情况?我能找到的唯一不存在序言的情况是没有任何东西写给作者(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)
        );
    

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        正如有人已经指出的那样,不带编码参数的调用就可以解决问题。 但是,如果你想明确一点,试试这个:

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

        要禁用 BOM,关键是使用 new UTF8Encoding(false) 构造,而不仅仅是 Encoding.UTF8Encoding。这和在没有编码参数的情况下调用 StreamWriter 一样,在内部它只是在做同样的事情。

        要启用 BOM,请改用 new UTF8Encoding(true)

        更新:从 Windows 10 v1903 开始​​,当在 notepad.exe 中保存为 UTF-8 时,BOM 字节现在是一个可选功能。

        【讨论】:

        • 我不明白 - 导致 C# 语法错误的答案如何在六年内获得 64 票赞成,而没有人提到它会导致语法错误?
        • 哈哈,我想这给读者留下了一个练习题:P 我想我已经修复了这个错误。
        • 另一种修复方法是using (var sw = new StreamWriter("text.txt", false, new UTF8Encoding(false)))
        • new UTF8Encoding(false) 是重要的一点。你不真的相信人们只是复制粘贴东西,对吗?
        • 为什么是假的?应该是真的。请在下面查看 Nik 的答案。我没明白,这个答案怎么能得到最高票,因为它提供了相反的答案。
        【解决方案6】:

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

        当在UTF8 属性返回的Encoding 类的实例上调用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 写入底层流。

        【讨论】:

        • 编码是我们程序中的一个用户设置(它通过 TCP 发送文本消息)...它是通过使用 enc = Encoding.GetEncoding(...) 的简单解析来检索的。我发现的唯一方法是在它后面添加if (enc is UTF8Encoding) enc = new UTF8Encoding(false);。虽然是一个相当肮脏的修复,但我认为没有其他方法可以解决它......
        • @Nyerguds 这不是唯一的方法。您可以将编码的获取抽象为一个给定参数的接口,获取编码。然后,您将该接口的实现传递/注入到您的代码中。然后它使一切都变得非常干净。
        • 这只是将同一件事移到不同的班级。总的来说,我只是觉得 GetEncoding 以某种方式设法不使用默认构造函数是非常奇怪的。啊,好吧。
        • 详细说明,GetPreambleStreamWriter 内部调用(参见the source),因此当调用UTF8 属性(由internallyUTF8Encoding(true) 构造)时,它返回 BOM,如答案中所述(另请参阅remarks 部分)。
        【解决方案7】:

        我的回答是基于 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Привет");
                }
            }
        

        【讨论】:

          【解决方案8】:

          我发现这个答案很有用(感谢@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();
          }
          

          【讨论】:

          • 我发现new UTF8Encoding(true) 构造函数很有用。
          【解决方案9】:

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

          如果 (!_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,不要 写
          // 序言。
          if (_stream.CanSeek && _stream.Position > 0)
          {
          _haveWrittenPreamble = true;
          }

          【讨论】:

            【解决方案10】:

            使用 Encoding.Default 而不是 Encoding.UTF8 解决了我的问题

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-02-24
              • 2012-04-17
              • 2016-02-04
              • 2020-05-29
              • 2012-06-21
              相关资源
              最近更新 更多