BOM与文件编码的识别

如何判断一个文件是何种编码类型

我们在上一篇文章中我们说过,要保证我们识别的内容不是乱码,那么在我们对字节序列byte[],进行解码时,需要采用正确的解码方法(即与生成序列时的编码方式相同)。

那么现在就出现了一个疑问,当我们打开一个文件的时候,如何知道这个文件在写入的时候是按照什么样的编码方式写入的呢?
即,如何识别一个文件的编码方式呢?
如果这个地方判断不正确,那么当程序(可能是操作系统)解析文件内容时,就会出现乱码。


//@1 BOM的什么

这里面就有一个概念:BOM(Byte Order Mark),字节序标志。
我们通常在文件的最开头,添加额外的几个无效字节,用来专门说明该文件是什么类型编码的文件。
那么,当我们读取文件的开头几个字节,就能明白接下来该用什么样的编码方式解析文件内容。

这里写图片描述

除此之外,微软Window系统也给UTF-8编码的文件添加了BOM,值为【EF BB BF】(虽然不需要这样画蛇添足)。

以UTF-16为例,我们看到有两种BOM 【FF FE】和【FE FF】分别对应Big endian(大端)和 Little endian(小端)。什么是大端和小端,为什么要区分大端和小端呢?


//@2 大端与小端

这就要从中央处理器的机制来说了。

以java为例,在java中int类型占4个字节,假如我们声明两个整数,0x12345678和0x11223344,那么我们该如何将这两个数存储在内存中呢?

于是产生了两种存入的方式:
1):

大端存储方式

由于存储时以字节为单元,int占4个字节,那就将占据的4个字节分别写入内存中,上面的这种存储方式就是从数字0x12和0x11的高位开始写入(高位写入低地址),这种写法就称为 Big endian(大端)。

既然能从高位写,那么就能从数字的地位写入,这种写法叫做Little endian(小端),如下:

小端的存储方式

所以对于同一个数,以大端和以小端的这两种方式的存储结果是不同的。

注意,大端和小端说的是中央处理器CPU对多字节数据(如,int 4字节,char 2字节等)的处理方式(存储和传输)。

举个简单的例子,
如下图,有一排方框,我们需要将我们的形状模型放到框中,对于小的圆形的模型,我们按顺序每个模型放入一个框内,这个没有疑问,但是对于一个大的三角形模型,需要占两个框,这时候放入的方法就不固定了,到底是大头在前呢,还是小头在前呢(貌似这两种都有道理),这就映射了CPU大小端存储的问题。

大端与小端

至今为止,大端和小端的问题依然没有统一,部分CPU采用的是大端的方式,另一部分是小端的方式。


//@3 为什么UTF-8 和 GBK没有大小端问题

说到这里,可能有的同学就有疑问了,
为什么UTF-8没有大端小端的问题呢?UTF-8不也是可以占有多个字节吗?多个字节写入的时候不是就存在大小端的问题吗?

注意:有无大小端问题是看写入的 数据个体 是不是多字节的,如int是4字节,char是2字节,这都是多字节的数据,区分大小端。

使用UTF-16编码,固定占两个字节,就相当于将java中的char直接写入到文件中,由于char占两个字节,那么写入时就有先写高位还是先写低位的问题。

如果一个字符使用utf-8表示,就需要将这个字符的Unicode码,编码成字节数组,这里要注意“字节”“数组”这两个概念,所以对于字节数组写入内存时,只需要按照数组的顺序,一个一个字节写入,不存在高位和低位的问题。
所以,一个汉字,在任何类型的CPU中生成的utf-8序列是一样的。

这也是我们常说的,UTF-8是单字节编码的,不用考虑字节序问题。

GBK也同理,需要参照区位表,将字符转化为字节数组,也属于单字节编码,不需要考虑字节序问题。

而UTF-32呢,固定的4个字节表示一个字符,相当于java中将int写入到内存中,
这也存在先写高位,还是先写低位的问题,固也区分大端小端。

到此为止,我们打开一个文件,如果看到有BOM,就能通过识别BOM,来判断该文件是UTF-16还是UTF-32,是用大端的方式解析,还是小端的方式解析。


//@4 无BOM的文件的识别

但是这里有个问题,对于GBK和UTF-8这两种编码方式,即不区分大端小端,又没有BOM,那我们怎么判断这个文件是GBK还是UTF-8呢?

那这就得从GBK和UTF-8的编码结果上来说了,

先看UTF-8:
UTF-8是一种多字节编码的字符集,表示一个Unicode字符时,它可以是1个至多个字节,在表示上有规律:

1字节:0xxxxxxx
2字节:110xxxxx 10xxxxxx
3字节:1110xxxx 10xxxxxx 10xxxxxx
4字节:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

通过识别头部,就能知道以几个字节为单位进行解析,并且头部只可能出现这几种情况,之后的每个字节均是以10开头的,规律非常明显。
这样就可以根据上面的特征对字符串进行遍历来判断一个字符串是不是UTF-8编码了。另外需要说明的是,后面的xxx也不是随意取值的,都是有固定范围的。

再看GBK:

GBK编码就是基于区位码的,用双字节编码表示中文和中文符号。一般编码方式是:0xA0+区号,0xA0+位号。如下表中的 “安”,区位号是1618(十进制),那么“安”字的GBK编码就是 0xA0+16 0xA0+18 也就是 0xB0 0xB2 。

这里写图片描述

虽然是双字节显示,但是GBK的每个字节的取值也是有范围的,GBK总体编码范围为0x8140~0xFEFE,首字节在 0x81~0xFE 之间,尾字节在 0x40~0xFE 之间,剔除 xx7F 一条线。

通过以上,我们可以看出虽然GBK和UTF-8的文件没有标记,但是他们的编码结果都是有鲜明特征的,我们可以很容易的通过查看文件字节序列来判断该文件是GBK还是UTF-8编码的。

像我们的操作系统,IDE环境、nodepad++等日常软件都能识别文件类型,从而正确的显示文件。


// @5 UTF-8的BOM

仔细的同学可能发现了,前面说过,在微软window操作系统中,会给utf-8文件添加BOM【EF BB BF】(虽然不需要这么做),这并不是说明UTF-8需要字节序,而是仅仅表名该文件是utf-8编码的文件。

打开记事本,随便写几个字,然后另存文件,选择“utf-8”编码格式。然后通过utralEdit的16进制格式来查看文件内容,如下:

UTF-8 BOM

但是微软这个举动,给我们会在不经意间给我们带来很多意外。
例如,
1)我在写shell脚本时,如果将脚本文件存储为带有BOM的utf-8格式的文件,那么在执行这个脚本文件时,就会因为文件头多了【EF BB BF】这三个字节而报错。

2)当我们用浏览器打开html文件时,默认按照文件meta标签中charset所设置的编码格式来解析文件内容。所以,charset的格式必须与本文件存储时的编码格式相同,不然会出现中文乱码。

但这里就有个意外的情况,当我们使用编辑器,如Sublime Text,生成一个html文件,html中meta标签设置的编码格式为gbk,用浏览器打开,我们会看到中文乱码。
这就是因为文件的编码格式(utf-8),与解析时的解码格式(gbk)不同。

但是当我们使用window的记事本打开该文件,不做任何操作,然后Ctrl+s保存该文件。然后我们再次刷新浏览器时,发现乱码不见了!
猛的一看,不明白为什么,
仔细一想,原来我们用记事本保存文件时,虽然没有进行任何操作,但是window默默的帮我们在文件头添加了UTF-8的字节序标记。这样当我们浏览器再次打开文件时,看到UTF-8的字节序,就不管里面的meta标签了,默认按照UTF-8的格式解析文件,就不会出现乱码了。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值