向字符串/ Blob 添加 UTF-8 BOM

93

我需要在客户端生成的文本数据中添加UTF-8字节顺序标记,该如何实现?

使用new Blob(['\xEF\xBB\xBF' + content])会得到'"my data"'

即使是'\uBBEF\x22BF'(其中'\x22' == '"'content中的下一个字符),也不起作用。

能否在JavaScript中为生成的文本添加UTF-8 BOM?

是的,在这种情况下我确实需要UTF-8 BOM。

6个回答

182

在字符串前面加上\ufeff。请参阅http://msdn.microsoft.com/en-us/library/ie/2yfce773(v=vs.94).aspx以获取更多信息。

请查看@jeff-fischer@casey之间的讨论关于UTF-8和UTF-16的详细信息和BOM。使上述内容正常工作的实际原因是,无论使用UTF-8还是UTF-16,字符串\ufeff始终用于表示BOM。

请参阅The Unicode Standard 5.0,第2章中的第36页以获取详细解释。从该页面摘录:

在表2-4中,UTF-8的字节序条目标记为N/A,因为UTF-8码元大小为8位,较大码元的机器字节序问题不适用。字节的序列化顺序不得偏离UTF-8编码形式定义的顺序。对于UTF-8,不需要也不建议使用BOM,但在将UTF-8数据从使用BOM的其他编码形式转换或BOM用作UTF-8签名的上下文中可能会遇到。

4
给其他阅读者的一个警告:注意,\ufeff 实际上是 UTF-16 BOM (字节顺序标记),而不是 UTF-8 BOM。详情请参考维基百科:Byte order mark。 - carlosrafaelgn
很棒的代码片段,适用于BOM编码,并且运行良好! @carlosrafaelgn 你是对的... 我想制作一个带有制表符分隔符的tsv文件,而UTF-8的制表符字符是/t。UTF-16 BE(BOM)相同的字符不起作用,我找不到相应的字符...你知道在哪里找或者\t是什么字符吗? 谢谢...! - menepet
1
由于 @mEnE 中的制表符 (codepoint U+0009) 小于 127,因此在 UTF-8 中,\t 的值为 0x09,就像在 UTF-16 中一样(0x0009)。唯一的区别是字节的物理存储顺序。在 UTF-8 中为 0x09,在 UTF-16 LE 中为 0x09、0x00,在 UTF-16 BE 中为 0x00、0x09。 - carlosrafaelgn
13
仅需要澄清一下:字符 \uFEFF 是所有 UTF(8、16 LE 和 16 BE)的字节顺序标记(BOM)字符。但是,它被编码为以下字节:
  • 0xEF 0xBB 0xBF
  • 0xFF 0xFE
  • 0xFE 0xFF 分别代表不同的编码方式。
重要的是要区分内部 Unicode 字符(\ufeff)和以字节表示该字符的各种方式。 :)
- Vbakke
非常感谢。我已经搜索了一段时间这个!! - no-stale-reads
显示剩余3条评论

51

我也遇到了同样的问题,这是我想出的解决方案:

var blob = new Blob([
                    new Uint8Array([0xEF, 0xBB, 0xBF]), // UTF-8 BOM
                    "Text",
                    ... // Remaining data
                    ],
                    { type: "text/plain;charset=utf-8" });

使用Uint8Array可以防止浏览器将这些字节转换为字符串(在Chrome和Firefox上测试过)。

您应该将text/plain替换为您所需的MIME类型。


这是在使用Blob或处理实际字节而不是JS字符串时的正确方法。当您使用JS字符串而不是实际字节时,Erik和Jeff的答案是正确的。 - Timothy Zorn

25

我正在编辑我的原始回答。上面的回答确实需要详细解释,因为这是Node.js中的一个复杂解决方案。

简单的答案是,是的,这段代码是有效的。

长的答案是,不,FEFF不是utf-8的字节顺序标记。显然,Node在编写文件编码时采取了某种快捷方式。FEFF是UTF16 Little Endian编码,可以在Byte Order Mark维基百科文章中看到,并且也可以在二进制文本编辑器中写入文件后查看。我已经验证了这一点。

http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding

显然,Node.JS使用\ufeff来表示任意数量的编码。它接受\ufeff标记,并根据writeFile的第3个选项参数将其转换为正确的字节顺序标记。您传递给编码字符串的第3个参数。Node.JS将此编码字符串并将\ufeff固定字节编码转换为任何一个实际编码的字节顺序标记。

UTF-8示例:

fs.writeFile(someFilename, '\ufeff' + html, { encoding: 'utf8' }, function(err) {
   /* The actual byte order mark written to the file is EF BB BF */
}

UTF-16 小端序示例:

fs.writeFile(someFilename, '\ufeff' + html, { encoding: 'utf16le' }, function(err) {
   /* The actual byte order mark written to the file is FF FE */
}

所以,正如你所看到的,\ufeff只是一个标记,表示任意数量的结果编码。实际上写入文件中的编码直接依赖于指定的编码选项。字符串中使用的标记与写入文件的内容无关。

我猜想这样做的原因是因为他们选择不写字节顺序标记,而UTF-8的3个字节标记不容易编码成要写入磁盘的javascript字符串。所以他们使用UTF16LE BOM作为占位符标记,在写入时被替换。


2
如果你看一下字节顺序标记和我最初说的话,就会发现是正确的。FEFF字节顺序标记不是UTF-8的字节顺序标记,正如你在问题中所述。原始答案似乎已经偶然得到了正确的答案,或者至少没有详细说明。他们之所以得到正确的答案,仅仅是因为选项编码默认为utf-8,而不是因为他们提供的字节顺序标记实际上是一个UTF-8字节顺序标记。 - Jeff Fischer
2
我有点困惑,因为这个问题根本没有提到节点。 - Casey
1
这并不是特定于Node的问题;我认为你对字节顺序标记有点困惑。 - Casey
10
具体来说,您可以在此处查看(http://en.wikipedia.org/wiki/Byte_order_mark),BOM始终是相同的字符(U+FEFF),而不是根据文本所使用的Unicode类型或字节序而有不同的字符。虽然写入的字节不同,但这是因为使用不同的编码写入相同的字符。 - Casey
2
已在被接受的答案中添加了更多详细信息,以阐述为什么这样可以实现。请根据需要进行编辑。 - Erik Töyrä Silfverswärd
显示剩余4条评论

11

这是我的解决方案:

var blob = new Blob(["\uFEFF"+csv], {
type: 'text/csv; charset=utf-18'
});

3
请问您能否解释一下为什么这个有效,并且utf-18是一个有效的编码吗? - Bryan Lee
我认为 charset=utf-18 是个打字错误。 - foxiris

2
这对我有用:

这适用于我:

let blob = new Blob(["\ufeff", csv], { type: 'text/csv;charset=utf-8' });

BOM(字节顺序标记)可能是必要的,因为一些程序需要它来使用正确的字符编码。

例如: 在默认字符编码为Shift_JIS而不是UTF-8的系统中,在MS Excel中打开没有BOM的csv文件时,它将以默认编码打开。这将导致垃圾字符。如果你指定UTF-8的BOM,它就会修复它。


0

这对我来说解决了问题。使用authorize.net api和cloudflare workers时,我遇到了BOM:

const data = JSON.parse((await res.text()).trim());


问题是如何添加BOM,而不是如何去除它。 - Kijewski
问题是如何添加BOM,而不是如何去除它。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接