为什么可以用\ ufeff替换UTF-8 BOM字节efbbbf?

时间:2019-01-18 03:32:32

标签: java byte-order-mark

在Windows中,UTF-8编码的文件具有BOM(字节顺序标记):EF BB BF。

许多消除此问题的解决方案只是一个简单的单行代码:

 replace("\uFEFF", "")

我不明白这为何起作用。

这是我的测试代码,替换后我检查了二进制文件,发现EF BB BF是否确实删除。太神奇了。为什么?

@Test
public void shit() throws Exception{
    byte[] b = new byte[]{-17,-69,-65, 97,97,97};//EF BB BF 61 61 61
    char[] c = new char[10];
    new InputStreamReader(new ByteArrayInputStream(b),"UTF-8").read(c);
    byte[] bytes = new StringBuilder().append(c).toString().replace("\uFEFF", "").getBytes();//
    for(byte bt: bytes){//61 61 61, we can see EF BB BF is indeed removed
        System.out.println(bt);
    }
}

2 个答案:

答案 0 :(得分:2)

InputStreamReader正在将UTF-8编码的字节序列(b)解码为UTF-16BE,并在此过程中将UTF-8 BOM转换为UTF-16BE BOM(\ uFEFF)。选择UTF-16BE作为目标编码,因为字符集默认为以下行为:

https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html

  

UTF-16字符集由RFC 2781指定;转型   它们所基于的格式在ISO的修订1中指定   10646-1,并且在Unicode标准中也有描述。

     

UTF-16字符集使用16位数量,因此   对字节顺序敏感。在这些编码中,流的字节顺序   可以用表示的初始字节顺序标记表示   Unicode字符'\ uFEFF'。字节顺序标记的处理方式如下:

     

解码时,UTF-16BE和UTF-16LE字符集解释   初始字节顺序标记为零宽度不间断空间;什么时候   编码,它们不写字节顺序标记。

     

解码时,UTF-16字符集解释字节序标记为   输入流的开始以指示字节的字节顺序   流,但如果没有字节序标记,则默认为big-endian;什么时候   编码,它使用big-endian字节顺序并写入big-endian   字节顺序标记。

请参阅JLS 3.1以了解为什么String的内部编码为UTF-16:

https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.1

  

Java编程语言以16位序列表示文本   代码单元,使用UTF-16编码。

String#getBytes()以平台的默认编码返回一个字节序列,该序列对于您的系统似乎是UTF-8。

摘要

使用 InputStreamReader 将字节序列解码为 String 时,将序列EF BB BF(UTF-8 BOM)转换为FE FF(UTF-16BE BOM),因为在存在BOM的情况下,默认情况下 Charset java.lang.String 编码为UTF-16 BE。替换UTF-16BE BOM并调用 String#getBytes()后,该字符串将解码为UTF-8(平台的默认字符集),您会看到没有BOM的原始字节序列。

答案 1 :(得分:1)

原因是unicode文本应以字节顺序标记开头(不建议使用UTF-8除外)。

来自维基百科

  

字节顺序标记(BOM)是 Unicode字符U + FEFF 字节顺序标记(BOM),它在文本流开始时显示为魔术数字...
  ...
  BOM与文档的其余部分采用相同的方案编码 ...

这意味着此特殊字符(\uFEFF)也必须以UTF-8编码。

UTF-8可以将Unicode代码点编码为一到四个字节。

  • 可以用7位表示的代码点被编码在一个字节中,最高位始终为零0xxx xxxx
  • 根据位的数目以多个字节编码的所有其他代码点,第一个字节的左置位代表用于编码的字节数,例如110x xxxx表示编码由两个字节表示,连续字节始终以10xx xxxx开头(x位可用于代码点)

U+0000 - U+007F范围内的代码点可以用一个字节编码。
U+0080 - U+07FF范围内的代码点可以用两个字节编码。 U+0800 - U+FFFF范围内的代码点可以用三个字节编码。

有关Wikipedia

的详细说明

对于BOM,我们需要三个字节。

hex    FE       FF
binary 11111110 11111111

以UTF-8编码位

pattern for three byte encoding 1110 xxxx  10xx xxxx  10xx xxxx
the bits of the code point           1111    11 1011    11 1111
result                          1110 1111  1011 1011  1011 1111
in hex                          EF         BB         BF

EF BB BF听起来已经很熟悉了。 ;-)

字节序列EF BB BF只不过是用UTF-8编码的BOM。

由于字节顺序标记对于UTF-8没有意义,因此在Java中未使用。

将BOM表字符编码为UTF-8

jshell> "\uFEFF".getBytes("UTF-8")
$1 ==> byte[3] { -17, -69, -65 }  // EF BB BF

因此,在读取文件时,字节序列将解码为\uFEFF

用于编码,例如添加了UTF-16 BOM表

jshell> " ".getBytes("UTF-16")
$2 ==> byte[4] { -2, -1, 0, 32 }  // FE FF + the encoded SPACE