原文地址:https://wyiyi.github.io/amber/2021/01/13/unicode/
description: "很多事來(lái)不及思考冬骚,就這樣自然發(fā)生了"
date: 2021.02.28 10:26
categories:
- Others
tags: [Others]
keywords: BOM, UTF8 with BOM, byte order mark
遇到的問(wèn)題:在單元測(cè)試中執(zhí)行sql文件逼蒙,sql的內(nèi)容是正確的袭灯,但是執(zhí)行報(bào)錯(cuò)记焊。扎心。
重現(xiàn)該場(chǎng)景,關(guān)鍵代碼如下:完整實(shí)例可見(jiàn)倉(cāng)庫(kù)
@SpringBootTest
class DemoTest {
@BeforeEach
@Sql("/com/amber/demo/init.sql")
// 建表語(yǔ)句: drop table if exists USER; create table USER(ID int(11) NOT NULL AUTO_INCREMENT, NAME VARCHAR, SEX VARCHAR,ADDR VARCHAR);
void test(){
assert true;
}
@Test
@Sql("/com/amber/demo/insert.sql")
// insert語(yǔ)句:INSERT INTO USER(ID, NAME, SEX, ADDR) VALUES (1, 'liming', 'men', 'jinzhou')
void insert(){
assert true;
}
@Ignore
@Sql("/com/amber/demo/utf8bom.sql")
// insert語(yǔ)句:INSERT INTO USER(ID, NAME, SEX, ADDR) VALUES (2, 'anc', 'man', 'shanghai'),保存為 UTF-8 with BOM 的編碼格式脑溢,失敗
void testBom(){
Exception exception = assertThrows(RuntimeException.class, () -> {
Integer.parseInt("1a");
});
String expectedMessage = "Failed to execute SQL script statement";
String actualMessage = exception.getMessage();
assertTrue(actualMessage.contains(expectedMessage));
}
}
在執(zhí)行testBom()
的過(guò)程中報(bào)錯(cuò)如下:
org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #1 of class path resource [com/amber/demo/utf8bom.sql]: 锘縄NSERT INTO USER(ID, NAME, SEX, ADDR) VALUES (2, 'anc', 'man', 'shanghai'); nested exception is org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "锘縄NSERT[*] INTO USER(ID, NAME, SEX, ADDR) VALUES (2, 'anc', 'man', 'shanghai')";
根據(jù)日志發(fā)現(xiàn)多了一些亂碼的字符,將sql的文件以十六進(jìn)制打開(kāi)后,發(fā)現(xiàn)在開(kāi)頭處有多余的字符 EF BB BF
(文件編碼格式顯示為 UTF-8 with BOM
)屑彻,將文件重新保存成UTF-8的編碼格式验庙,執(zhí)行成功。
原來(lái)是 BOM 在作祟社牲。
什么是 BOM
BOM(Byte-Order Mark)
即字節(jié)順序標(biāo)記粪薛,出現(xiàn)在文本文件頭部, Unicode編碼標(biāo)準(zhǔn)中用于標(biāo)識(shí)文件是采用哪種格式的編碼搏恤,但它對(duì)于文件的讀者來(lái)說(shuō)是不可見(jiàn)字符违寿。
摘自Wikipedia:
The byte order mark (BOM) is a particular usage of the special Unicode character, U+FEFF BYTE ORDER MARK, whose appearance as a magic number at the start of a text stream can signal several things to a program reading the text:[1]
- The byte order, or endianness, of the text stream in the cases of 16-bit and 32-bit encodings;
- The fact that the text stream's encoding is Unicode, to a high level of confidence;
- Which Unicode character encoding is used.
BOM use is optional. Its presence interferes with the use of UTF-8 by software that does not expect non-ASCII bytes at the start of a file but that could otherwise handle the text stream.
摘自Unicode:
A: A byte order mark (BOM) consists of the character code U+FEFF at the beginning of a data stream, where it can be used as a signature defining the byte order and encoding form, primarily of unmarked plaintext files. Under some higher level protocols, use of a BOM may be mandatory (or prohibited) in the Unicode data stream defined in that protocol. [AF]
為什么會(huì)存在 BOM
- UTF-16、UTF-32是以2個(gè)字節(jié)和4個(gè)字節(jié)為單位進(jìn)行處理的熟空, 即1次讀取2個(gè)字節(jié)或4個(gè)字節(jié)藤巢, 這樣一來(lái), 在存儲(chǔ)和網(wǎng)絡(luò)傳輸時(shí)就要考慮1個(gè)單位內(nèi)2個(gè)字節(jié)或4個(gè)字節(jié)之間順序的問(wèn)題息罗。
- UTF-8編碼是以1個(gè)字節(jié)為單位進(jìn)行處理的掂咒,不會(huì)受CPU大小端的影響。UTF-8 不需要 BOM 來(lái)表明字節(jié)順序迈喉, 但可以用 BOM 來(lái)表明編碼方式绍刮。 字符 “Zero Width No-Break Space” 的 UTF-8 編碼是 EF BB BF。
所以如果接收者收到以 EF BB BF 開(kāi)頭的字節(jié)流挨摸, 就知道這是 UTF-8編碼了孩革。 Windows 就是使用 BOM 來(lái)標(biāo)記文本文件的編碼方式的。
UTF-8 BOM 長(zhǎng)什么樣
- 無(wú)論 Unicode 文本如何轉(zhuǎn)換得运, BOM都可以用作簽名: UTF-8膝蜈, UTF-16, 或UTF-32等澈圈。包含BOM的字節(jié)將是由該轉(zhuǎn)換格式轉(zhuǎn)換為Unicode字符
U + FEFF
的任何字節(jié)。
在下列表格中帆啃, 表示BOM 的 Unicode 以及它的十六進(jìn)制瞬女。
編碼 | 表示(十六進(jìn)制) |
---|---|
UTF-8 | EF BB BF |
UTF-16 (BE) | FE FF |
UTF-16 (LE) | FF FE |
UTF-32 (BE) | 00 00 FE FF |
UTF-32 (LE) | FF FE 00 00 |
UTF-7 | 2B 2F 76 |
UTF-1 | F7 64 4C |
UTF-EBCDIC | DD 73 66 73 |
SCSU | 0E FE FF |
... | ... |
怎么查看 BOM
- BOM 頭在記事本中是看不到的,可以使用以下工具查看努潘,文本中字符內(nèi)容均為 abc :
- 使用十六進(jìn)制編輯工具進(jìn)行查看
- 亦可使用Total Commander 文件管理工具诽偷, 查看文件, 選擇options疯坤, 即可查看各種Unicode格式
2.在linux 中查看 BOM
- 找到對(duì)應(yīng)的文件位置
- 查找當(dāng)前包含 BOM 頭的文件:
$ grep -r $'^\xEF\xBB\xBF'
bom.txt:abc
- 查看文件相關(guān)信息
$ ll bom.txt
-rw-rw-r-- 1 xxx xxx 6 Dec 18 16:22 bom.txt
$ file bom.txt
bom.txt: UTF-8 Unicode text, with no line terminators
$ file 16be.txt
16be.txt: Big-endian UTF-16 Unicode text, with no line terminators
...
- 使用
vi
打開(kāi)查看文件內(nèi)容 - 查看
bom.txt
文件的十六進(jìn)制:%!xxd
顯示內(nèi)容:
0000000: efbb bf61 6263 0a ... abc.
其中包含EF BB BF
即為 BOM 標(biāo)記
如何添加或去掉 BOM
1.Windows BOM 操作:
- 增加 BOM 編碼格式:
新建一個(gè)文件布卡,輸入abc
保存時(shí)選擇使用 UTF-8、UTF-8 with BOM雇盖、UTF-16 LE 或者 UTF-16 BE 等格式(以 VS Code 為例) - 去掉 BOM 編碼格式:
可通過(guò)程序控制過(guò)濾掉BOM:存在 BOM 字符相關(guān)則去掉
2.linux BOM 命令操作:
- utf8.txt 加上 BOM 的編碼格式
$ file utf8.txt
utf8.txt: ASCII text, with no line terminators
# 用 vi 打開(kāi)文件
# 設(shè)置 bom 格式忿等,執(zhí)行命令 :set bomb
# 保存并退出 vi :wq!
$ file utf8.txt
utf8.txt: UTF-8 Unicode (with BOM) text
- bom.txt 去掉 BOM 的編碼格式
$ file bom.txt
bom.txt: UTF-8 Unicode (with BOM) text, with no line terminators
# 用 vi 打開(kāi)文件
# 設(shè)置無(wú) bom 格式, 執(zhí)行命令 :set nobomb
# 保存并退出 vi崔挖,執(zhí)行命令 :wq!
$ file bom.txt
bom.txt: ASCII text
- bom.txt 的 UTF-8 with BOM 編碼格式修改為 UTF-16 Little-endian 或者 UTF-16 Big-endian 的編碼格式
$ file bom.txt
bom.txt: UTF-8 Unicode (with BOM) text, with no line terminators
# 用 vi 打開(kāi)文件
# 設(shè)置 UTF-16 Little-endian 格式贸街,執(zhí)行命令 :set fileencoding=utf-16le
# 保存并退出 vi :wq!
$ file bom.txt
bom.txt: Little-endian UTF-16 Unicode text, with no line terminators
# 設(shè)置 UTF-16 Big-endian 格式,執(zhí)行命令① :set fileencoding=utf-16 或者② :set fileencoding=utf-16be
# 保存并退出 vi :wq!
$ file bom.txt
bom.txt: Big-endian UTF-16 Unicode text
...
Linux 和 Windows 關(guān)于 BOM 的區(qū)別
- Linux 默認(rèn)的編碼格式為 UTF-8狸相。
Linux 保存文件的編碼格式為UTF-8薛匪,如:abc.txt 查看編碼格式:
abc.txt: UTF-8 Unicode text
- Windows 默認(rèn)的編碼格式為 GBK。
Windows 自帶的記事本等軟件卷哩, 在保存一個(gè)以UTF-8編碼的文件時(shí)蛋辈, 會(huì)在文件開(kāi)始的地方插入三個(gè)不可見(jiàn)的字符(0xEF 0xBB 0xBF, 即BOM)将谊。 如: utf8.txt
BOM 不是明智的選擇
UTF-8 BOM 是文本流(0xEF冷溶、0xBB、0xBF) 開(kāi)始時(shí)的字節(jié)序列尊浓,允許讀取器更可靠地猜測(cè)文件在 UTF-8 中編碼逞频。
雖然BOM字符起到了標(biāo)記文件編碼的作用但它并不屬于文件的內(nèi)容部分, 所以會(huì)產(chǎn)生一些問(wèn)題:
- BOM 用來(lái)表示編碼的字節(jié)序栋齿,但是由于字節(jié)序?qū)?UTF-8 無(wú)效苗胀,因此不需要 BOM。
- BOM 不僅在 JSON 中非法且破壞了JSON 解析器瓦堵。
- BOM 會(huì)阻斷一些腳本: Shell scripts基协, Perl scripts, Python scripts菇用, Ruby scripts澜驮, Node.js。
- BOM 對(duì) PHP 很不友好: PHP 不能識(shí)別 BOM 頭惋鸥,且不會(huì)忽略BOM杂穷, 所以在讀取、包含或者引用這些文件時(shí)卦绣,會(huì)把BOM作為該文件開(kāi)頭正文的一部分耐量。 根據(jù)嵌入式語(yǔ)言的特點(diǎn), 這串字符將被直接執(zhí)行(顯示)出來(lái)滤港。 由于頁(yè)面的
top padding
為0廊蜒, 導(dǎo)致無(wú)法讓整個(gè)網(wǎng)頁(yè)緊貼瀏覽器頂部。
2.6 Encoding Schemes
... Use of a BOM is neither required nor recommended for UTF-8, but may be encountered in contexts where UTF-8 data is converted from other encoding forms that use a BOM or where the BOM is used as a UTF-8 signature.
See the "Byte Order Mark" subsection in Section 16.8, Specials, for more information.
根據(jù) Unicode標(biāo)準(zhǔn) 不建議使用 UTF-8 文件的 BOM,所以在將文件保存為 UTF-8 的編碼格式時(shí)劲藐,一定要注意一般不使用 UTF-8 with BOM 的編碼格式八堡。