Java如何识别上传文件的编码,BOM头又是什么?

     背景:

     最近在做一个关于上传文件,需要识别文件编码的场景需求,项目中使用org.springframework.web.multipart.commons.CommonsMultipartFile来接收上传上件对象,此对象并没有类似getFileCharset()等相关的获取文件编码的API。因此,在网上补了一下功课,了解一下,识别文件编码的常用方案,总结于此,以备后查,仅供参考。

一、BOM是什么

     BOM,即字节顺序标记(ByteOrderMark)是用来判断文本文件是哪一种Unicode编码的标记,其本身是一个Unicode字符("\uFEFF"),位于文本文件头部。

     BOM头是微软公司为了解决不同的编码冲撞问题引入的。比如,当你新建一个 文本文档 之后,在里面输入 “联通” 两个字,然后保存。当你再次打开的时候,原来输入的 “联通” 会变成两个乱码。这就是因为 GB2312 编码与 UTF8 编码产生了编码冲撞造成的。

     在UTF-8编码文件中BOM在文件头部,占用三个字节,用来标示该文件属于UTF-8编码。UTF-8 的BOM对UFT-8没有作用,是为了支持UTF-16,UTF-32才加上的BOM,BOM签名的目的是告诉编辑器当前文件采用何种编码,方便编辑器识别,但是BOM虽然在编辑器中不显示,但是会产生输出,有些软件不识别BOM的头,打开保存后会识别出错。

在不同的Unicode编码中,对应的BOM的二进制字节Bytes Encoding如下:

FE FF UTF16BE
FF FE UTF16LE
EF BB BF UTF8

因此,我们可以根据文件头部的几个字节和上面的表格对应来判断该文件是哪种编码形式。

二.、如何获取文件的BOM字符

    BOM头在记事本中是看不到的,通过程序读取文件是可以识别的,也可以打印出来

// <Buffer ef bb bf 68 65 6c 6c 6f 20 77 6f 72 6c 64>
// 前三个字节就是对应的我们UTF8编码的文本的BOM头字符

   参考JAVA识别代码:

/**
* 判断文件编码
*
* @param inputStream
* @return
*/
private String getFilecharset(InputStream inputStream) {
String charset = "GBK";
byte[] first3Bytes = new byte[3];
try {
boolean checked = false;
BufferedInputStream bis = new BufferedInputStream(inputStream);
bis.mark(0);
int read = bis.read(first3Bytes, 0, 3);
if (read == -1) {
return charset; // 文件编码为 ANSI
} else if (first3Bytes[0] == (byte) 0xFF
&& first3Bytes[1] == (byte) 0xFE) {
charset = "UTF-16LE"; // 文件编码为 Unicode
checked = true;
} else if (first3Bytes[0] == (byte) 0xFE
&& first3Bytes[1] == (byte) 0xFF) {
charset = "UTF-16BE"; // 文件编码为 Unicode big endian
checked = true;
} else if (first3Bytes[0] == (byte) 0xEF
&& first3Bytes[1] == (byte) 0xBB
&& first3Bytes[2] == (byte) 0xBF) {
charset = "UTF-8"; // 文件编码为 UTF-8
checked = true;
}
bis.reset();
if (!checked) {
int loc = 0;
while ((read = bis.read()) != -1) {
loc++;
if (read >= 0xF0)
break;
if (0x80 <= read && read <= 0xBF) // 单独出现BF以下的,也算是GBK
break;
if (0xC0 <= read && read <= 0xDF) {
read = bis.read();
if (0x80 <= read && read <= 0xBF) // 双字节 (0xC0 - 0xDF)
// (0x80
// - 0xBF),也可能在GB编码内
continue;
else
break;
} else if (0xE0 <= read && read <= 0xEF) {// 也有可能出错,但是几率较小
read = bis.read();
if (0x80 <= read && read <= 0xBF) {
read = bis.read();
if (0x80 <= read && read <= 0xBF) {
charset = "UTF-8";
break;
} else
break;
} else
break;
}
}
}
bis.close();
} catch (Exception e) {
e.printStackTrace();
log.error("getFilecharset exception ",e);
}
return charset;

}

 

三、BOM头存在哪些问题

     虽然BOM头起到了标记文件编码的作用,但是他并不属于文件的内容部分,类似WINDOWS自带的记事本等软件,在保存一个以UTF-8编码的文件时,会在文件开始的地方插入三个不可见的字符(0xEF 0xBB 0xBF,即BOM)。它是一串隐藏的字符,用于让记事本等编辑器识别这个文件是否以UTF-8编码。对于一般的文件,这样并不会有什么问题。

但在某些场景下,会产生一些额外的问题:

     1、比如我们把几个JS文件合并成一个文件后,如果文件中间含有BOM字符,就会导致浏览器JS语法错误。

     2、对于 PHP来说,BOM是个大麻烦。PHP并不会忽略BOM,所以在读取、包含或者引用这些文件时,会把BOM作为该文件开头正文的一部分。根据嵌入式语言的特点,这串字符将被直接执行(显示)出来。由此造成即使页面的 top padding 设置为0,也无法让整个网页紧贴浏览器顶部,因为在html一开头有这3个字符呢!最大的麻烦还不是这个。受COOKIE送出机制的限制,在这些文件开头已经有BOM的文件中,COOKIE无法送出(因为在COOKIE送出前PHP已经送出了文件头),所以登入和登出功能失效。一切依赖COOKIE、SESSION实现的功能全部无效。

四、如何去掉文件中的BOM头

1、在文件另存为的时候选择无BOM头的UTF8编码

2、使用node中的文件模块获取文件的buffer数据并去掉前三个字节,代码如下:

function deleteUtf8BomHead(path) {
    let buf = fs.readFileSync(path);
    if (buf[0] == 0xef && buf[1] == 0xbb && buf[2] == 0xbf) {
        buf = buf.slice(3)
    }
    return buf
}

 3、EDITPLUS去BOM头的方法

    编辑器调整为UTF8编码格式后,保存的文件前面会多出一串隐藏的字符(也即是BOM),用于编辑器识别这个文件是否是以UTF8编码。

运行Editplus,点击工具,选择首选项,选中文件,UTF-8标识选择 总是删除签名,然后对PHP等文件编辑和保存后的PHP文件就是不带BOM的了。

4、UltraEdit去除BOM头办法

打开文件后,另存为选项的编码格式里选择(UTF-8 无BOM头)保存。

备注 :

     1、在编辑、更改任何文本文件时,请使用不会乱加BOM的编辑器。Linux下的编辑器目前没发现这个问题。Windows下,请勿使用记事本等编辑器。推荐的编辑器是:notepad ++ Editplus 2.12版本以上, EmEditor, UltraEdit(需要取消‘添加BOM’的相关选项), Dreamweaver(需要取消‘添加BOM’的相关选项)等。对于已经添加了BOM的文件,要取消的话,可以用以上编辑器另存一次。(Editplus需要先另存为GB,再另存为UTF-8。)

posted @ 2021-06-03 11:07  xuzhujack  阅读(1289)  评论(0编辑  收藏  举报
;