BOM(Byte Order Mark),字节顺序标记,出现在文本文件头部,Unicode编码标准中用于标识文件是采用哪种格式的编码。

起因

最近在对接一个三方项目,接口在接受三方上报的数据,解析json数据时报了个奇怪的错误。
接口是由golang写的,从http POST数据中读取字节数据,json.Unmarshal()反序列化报错:

invalid character 'ï' looking for beginning of value

但是日志输出的数据上看,又没有什么奇怪的字符。。。

原因

网上查了一下,应该是提交到接口的utf-8字符串带上了BOM头

utf-8文件本来并不需要BOM头,给utf-8文件加BOM是windows特有的一种奇葩方式,windows记事本会给utf-8格式的文件加上BOM头,用来标记编码格式加以区分

大概是对方开发人员图方便,使用了windows记事本修改了代码。

编码格式 BOM头标记
UTF-8 EF BB BF
UTF-16LE FF FE (小尾)
UTF-16BE FE FF (大尾)
UTF-32LE FF FE 00 00
UTF-32BE 00 00 FE FF

对于golang,解决的方法也很简单,把对应的BOM头标记去掉就可以了:

body = bytes.TrimPrefix(body, []byte("\xef\xbb\xbf"))

关于BOM

那么问题来了,linux不给utf-8文件加BOM头,windows却要这么做呢?

UTF本来作为一种纸上谈兵的规范,没有具体实现。微软是第一个支持并实现的,但是为了区分文件头信息,增加了自己定义的BOM。

bom的全称是字节序标记,对于多字节的编码,例如ucs2,ucs4,utf16,utf32,它用于标明字节序。

对,它的标准名称就是「字节序标记」,而不是「文件格式检测标记」。所以,单纯从定义上来说,utf8作为字节流编码,是不应该加bom的,字节流本身并无字节序的概念。

linux使用不带bom的utf8,一方面当然是因为utf8不需要bom,更重要的,是unix的设计逻辑没法兼容带bom的文本:一切皆文件,一切文件皆是流。

一个流可以被任意的切断,独立解析,而不会改变含义。所以它不能有头,也不能有结尾。由于头根本不存在,所以bom不允许存在,否则你把一个流切成一万份,就必须在一万个片段的前面加bom,这种对流内容的修改违背了设计。osx派生自bsd,而bsd也同样遵循unix设计思想,所以无论对于linux还是osx,bom必然不能存在,无论有没有微软,都只能用无bom的utf8作为标准,这是unix设计理念所决定的。

然而微软的系统缺省都是用户当前代码页,当前代码页不是utf8,这样,utf8作为非当前代码页格式就无法识别。utf8的bom是微软自己的一个创新,微软增加的一个识别码。虽然用字节序标记当识别码是不优雅的,但对windows来说这么做不会有任何副作用。依赖一个明确定义的文件头标记对Windows来说完全可接受。这是为了兼容微软的历史版本系统,而并不是刻意制造与unix血统系统的不兼容。结论:其实各个操作系统对待utf8的方式,都是与自己的过往设计,与自己的旧版本兼容,linux/osx是为了兼容unix的基础设计,windows则是为了兼容所有历史版本的windows。