StreamWriter 和 UTF-8 字节顺序标记
Posted
技术标签:
【中文标题】StreamWriter 和 UTF-8 字节顺序标记【英文标题】:StreamWriter and UTF-8 Byte Order Marks 【发布时间】: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 编码方案中的大端或小端数据. 您解决了这个问题吗?如果是这样,请标记正确答案或发布您自己的答案以帮助他人。 Create Text File Without BOM的可能重复 【参考方案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 以某种方式设法不使用默认构造函数是非常奇怪的。啊,好吧。
详细说明,GetPreamble
由StreamWriter
内部调用(参见the source),因此当调用UTF8
属性(由internally 和UTF8Encoding(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 解决了我的问题
【讨论】:
以上是关于StreamWriter 和 UTF-8 字节顺序标记的主要内容,如果未能解决你的问题,请参考以下文章
C#中FileStream和StreamWriter/StreamReader的区别