都是 “编码格式” 惹得祸
遇到的问题:在单元测试中执行sql文件,sql的内容是正确的,但是执行报错。扎心。
重现该场景,关键代码如下:完整实例可见仓库
1 | @SpringBootTest |
在执行testBom()
的过程中报错如下:
1 | 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 ('3', 'anc', 'man', 'shanghai'); nested exception is org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "锘縄NSERT[*] INTO USER(ID, NAME, SEX,ADDR) VALUES ('3', 'anc', 'man', 'shanghai')"; SQL statement: |
根据日志发现多了一些乱码的字符,将sql的文件用十六进制打开后,发现在开头处有不可见的字符 EF BB BF
,将文件重新保存成UTF-8的编码格式,执行成功。
原来是 BOM 在作祟。
什么是 BOM
BOM(Byte-Order Mark)
即字节顺序标记,出现在文本文件头部, Unicode编码标准中用于标识文件是采用哪种格式的编码,但它对于文件的读者来说是不可见字符。
摘自Wikipedia:
1 | 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] |
摘自Unicode:
1 | 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. |
为什么会存在 BOM
- UTF-16、UTF-32是以2个字节和4个字节为单位进行处理的, 即1次读取2个字节或4个字节, 这样一来, 在存储和网络传输时就要考虑1个单位内2个字节或4个字节之间顺序的问题。
- UTF-8编码是以1个字节为单位进行处理的,不会受CPU大小端的影响。UTF-8 不需要 BOM 来表明字节顺序, 但可以用 BOM 来表明编码方式。 字符 “Zero Width No-Break Space” 的 UTF-8 编码是 EF BB BF。
所以如果接收者收到以 EF BB BF 开头的字节流, 就知道这是 UTF-8编码了。 Windows 就是使用 BOM 来标记文本文件的编码方式的。
UTF-8 BOM 长什么样
- 无论 Unicode 文本如何转换, BOM都可以用作签名: UTF-8, UTF-16, 或UTF-32等。包含BOM的字节将是由该转换格式转换为Unicode字符
U + FEFF
的任何字节。
在下列表格中, 表示BOM 的 Unicode 以及它的十六进制。
编码 | 表示(十六进制) |
---|---|
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 头在记事本中是看不到的,可以使用以下工具查看,文本中字符内容均为 abc :
使用十六进制编辑工具进行查看
亦可使用Total Commander 文件管理工具, 查看文件, 选择options, 即可查看各种Unicode格式
2.在linux 中查看 BOM
- 找到对应的文件位置
- 查找当前包含 BOM 头的文件:
1
2$ grep -r $'^\xEF\xBB\xBF'
bom.txt:abc - 查看文件相关信息
1
2
3
4
5
6
7$ 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
打开查看文件内容 - 查看
bom.txt
文件的十六进制:%!xxd
显示内容:0000000: efbb bf61 6263 0a ... abc.
其中包含EF BB BF
即为 BOM 标记
如何添加或去掉 BOM
1.Windows BOM 操作:
- 增加 BOM 编码格式:
新建一个文件,输入abc
保存时选择使用 UTF-8、UTF-8 with BOM、UTF-16 LE 或者 UTF-16 BE 等格式(以 VS Code 为例) - 去掉 BOM 编码格式:
可通过程序控制过滤掉BOM:存在 BOM 字符相关则去掉
2.linux BOM 命令操作:
utf8.txt 加上 BOM 的编码格式
1
2
3
4
5
6
7$ file utf8.txt
utf8.txt: ASCII text, with no line terminators
# 用 vi 打开文件
# 设置 bom 格式,执行命令 :set bomb
# 保存并退出 vi :wq!
$ file utf8.txt
utf8.txt: UTF-8 Unicode (with BOM) textbom.txt 去掉 BOM 的编码格式
1
2
3
4
5
6
7$ file bom.txt
bom.txt: UTF-8 Unicode (with BOM) text, with no line terminators
# 用 vi 打开文件
# 设置无 bom 格式, 执行命令 :set nobomb
# 保存并退出 vi,执行命令 :wq!
$ file bom.txt
bom.txt: ASCII textbom.txt 的 UTF-8 with BOM 编码格式修改为 UTF-16 Little-endian 或者 UTF-16 Big-endian 的编码格式
1
2
3
4
5
6
7
8
9
10
11
12
13$ file bom.txt
bom.txt: UTF-8 Unicode (with BOM) text, with no line terminators
# 用 vi 打开文件
# 设置 UTF-16 Little-endian 格式,执行命令 :set fileencoding=utf-16le
# 保存并退出 vi :wq!
$ file bom.txt
bom.txt: Little-endian UTF-16 Unicode text, with no line terminators
# 设置 UTF-16 Big-endian 格式,执行命令① :set fileencoding=utf-16 或者② :set fileencoding=utf-16be
# 保存并退出 vi :wq!
$ file bom.txt
bom.txt: Big-endian UTF-16 Unicode text
...
Linux 和 Windows 关于 BOM 的区别
- Linux 默认的编码格式为 UTF-8。
Linux 保存文件的编码格式为UTF-8,如:abc.txt 查看编码格式:abc.txt: UTF-8 Unicode text
- Windows 默认的编码格式为 GBK。
Windows 自带的记事本等软件, 在保存一个以UTF-8编码的文件时, 会在文件开始的地方插入三个不可见的字符(0xEF 0xBB 0xBF, 即BOM)。 如: utf8.txt
BOM 不是明智的选择
UTF-8 BOM 是文本流(0xEF、0xBB、0xBF) 开始时的字节序列,允许读取器更可靠地猜测文件在 UTF-8 中编码。
虽然BOM字符起到了标记文件编码的作用但它并不属于文件的内容部分, 所以会产生一些问题:
BOM 用来表示编码的字节序, 但是由于字节序对 UTF-8 无效,因此不需要 BOM。
BOM 不仅在 JSON 中非法且破坏了JSON 解析器。
BOM 会阻断一些脚本: Shell scripts, Perl scripts, Python scripts, Ruby scripts, Node.js。
BOM 对 PHP 很不友好: PHP 不能识别 BOM 头, 且不会忽略BOM, 所以在读取、包含或者引用这些文件时, 会把BOM作为该文件开头正文的一部分。
根据嵌入式语言的特点, 这串字符将被直接执行(显示)出来。
由于页面的top padding
为0, 导致无法让整个网页紧贴浏览器顶部。2.6 Encoding Schemes
1
2... 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.
根据 Unicode标准 不建议使用 UTF-8 文件的 BOM,所以在将文件保存为 UTF-8 的编码格式时,一定要注意一般不使用 UTF-8 with BOM 的编码格式。
Title: 都是 “编码格式” 惹得祸
Author: Amber
Date: 2021-01-13
Last Update: 2024-10-29
Blog Link: https://wyiyi.github.io/amber/2021/01/13/unicode/
Copyright Declaration: Copyright © 2022 Amber.