在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);
}
}
答案 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代码点编码为一到四个字节。
0xxx xxxx
110x xxxx
表示编码由两个字节表示,连续字节始终以10xx xxxx
开头(x
位可用于代码点) U+0000 - U+007F
范围内的代码点可以用一个字节编码。
U+0080 - U+07FF
范围内的代码点可以用两个字节编码。
U+0800 - U+FFFF
范围内的代码点可以用三个字节编码。
对于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