我遇到了 StreamWriter 和字节顺序标记的问题。该文档似乎声明 Encoding.UTF8 编码启用了字节顺序标记,但是在写入文件时,有些有标记,而另一些则没有。
我正在通过以下方式创建流编写器:
this.Writer = new StreamWriter(this.Stream, System.Text.Encoding.UTF8);
任何关于可能发生的事情的想法将不胜感激。
我遇到了 StreamWriter 和字节顺序标记的问题。该文档似乎声明 Encoding.UTF8 编码启用了字节顺序标记,但是在写入文件时,有些有标记,而另一些则没有。
我正在通过以下方式创建流编写器:
this.Writer = new StreamWriter(this.Stream, System.Text.Encoding.UTF8);
任何关于可能发生的事情的想法将不胜感激。
正如有人已经指出的那样,在没有编码参数的情况下调用就可以了。但是,如果你想明确,试试这个:
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 字节现在是一个可选功能。
问题是由于您在class上使用静态UTF8
属性。Encoding
当对该属性返回的类的实例调用该GetPreamble
方法时,它会返回字节顺序标记(三个字符的字节数组)并在任何其他内容写入流之前写入流(假设是一个新流)。Encoding
UTF8
UTF8Encoding
您可以通过自己创建类的实例来避免这种情况,如下所示:
// As before.
this.Writer = new StreamWriter(this.Stream,
// Create yourself, passing false will prevent the BOM from being written.
new System.Text.UTF8Encoding());
根据默认无参数构造函数的文档(强调我的):
此构造函数创建一个不提供 Unicode 字节顺序标记的实例,并且在检测到无效编码时不会引发异常。
这意味着调用GetPreamble
将返回一个空数组,因此不会将 BOM 写入底层流。
我唯一一次看到构造函数不添加 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。
我的答案基于 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Привет");
}
}
您是否对每个文件都使用相同的 StreamWriter 构造函数?因为文档说:
要使用 UTF-8 编码和 BOM 创建 StreamWriter,请考虑使用指定编码的构造函数,例如 StreamWriter(String, Boolean, Encoding)。
前段时间我也遇到过类似的情况。我最终使用该Stream.Write
方法而不是 StreamWriter 并Encoding.GetPreamble()
在编写之前编写了结果Encoding.GetBytes(stringToWrite)
我发现这个答案很有用(感谢@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();
}
似乎如果文件已经存在并且不包含 BOM,那么在覆盖时它不会包含 BOM,换句话说,StreamWriter 在覆盖文件时会保留 BOM(或它不存在)。
您能否展示它不生产它的情况?我能找到的唯一不存在序言的情况是没有任何东西写给作者(吉姆米歇尔似乎找到了另一个,合乎逻辑的,更有可能是你的问题,看看它的答案)。
我的测试代码:
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)
);
使用 Encoding.Default 而不是 Encoding.UTF8 解决了我的问题
阅读 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 preamble = _encoding.Preamble;
if (preamble.Length > 0)
{
_stream.Write(preamble);
}
}
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;
}