Windows下记事本保存文本文件的时候,可以选择不同的编码格式来保存文件,各种编码保存的文件的二进制是不同的,举例说明:
我们在记事本中输入123,选择默认的编码格式,即ANSI,也就是系统默认的编码格式,简体中文版的默认编码格式为GBK,此时我们使用二进制工具打开时,其二进制形式为:
31 32 33
使用Unicode编码保存,实际上,这种称呼是不正确的,Unicode只是表示字符集方案,并不能表示编码方案,windows对Unicode实际上采用的编码方案是UTF-16LE,其会在文本的开头插入小段字节序标识BOM(FFFE),故其二进制为:
FF FE 31 00 32 00 33 00
使用Unicode big endian编码保存,这种称呼也是不正确的,windows实际上采用的编码方案是UTF-16BE,其会在文本的开头插入大端字节序标识BOM(FEFF),故其二进制为:
FE FF 00 31 00 32 00 33
使用UTF-8编码保存,这种称呼也是不正确的,正常UTF-8编码的二进制是没有BOM标识的,而windows上的UTF-8编码的文件时有UTF-8 BOM标识(EF BB BF),故其二进制为:
EF BB BF 31 32 33
下面请看由BOM头引起的问题的例子:
package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
public class Test1 {
public static void main(String[] args) throws IOException {
String myString = "";
byte[] bytes = new byte[10];
int readCount = 0;
try (FileOutputStream outputStream = new FileOutputStream("D:\\test\\hello.txt")) {
outputStream.write(new byte[] { -2, -1, 0, 0x31, 0, 0x32, 0, 0x33 });
outputStream.flush();
outputStream.close();
} catch (Exception e) {
}
try (FileInputStream reader = new FileInputStream("D:\\test\\hello.txt")) {
while ((readCount = reader.read(bytes, 0, 10)) != -1) {
myString += new String(bytes, 0, readCount, "UTF-16BE");
System.out.println(Arrays.toString(bytes));
System.out.println(myString);
System.out.println(Integer.parseInt(myString));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
该例子我们通过程序写入二进制数据:
FE FF 00 31 00 32 00 33
以UTF-16BE的方式读入,当我们将读取的字符串转化为数字时,出现错误了,其上面的输出结果如下:
[-2, -1, 0, 49, 0, 50, 0, 51, 0, 0] 123
java.lang.NumberFormatException: For input string: ”123” at
java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580) at
java.lang.Integer.parseInt(Integer.java:615) at
test.Test1.main(Test1.java:24)
其真正原因就是这个BOM字节序导致的,一般情况下很难发现这个错误,因为输出的字符串就是“123”,与正常的字符串结果看起来并没有什么不同,这时我们应该想到要查下其二进制表示,这样很快就能发现问题了。
最后,关于字节序BOM,上文提到各种不同的编码其字节序不同,实际上BOM是指一个Unicode character,其值为
U+FEFF,但是由于编码方式不同,其表示出来不同的值,但是都是映射到同一个Unicode字符集上了。
The byte order mark (BOM) is a Unicode character, U+FEFF Byte order mark (BOM), whose appearance as a magic number at the start of a text stream can signal several things to a program consuming the text。
代码为证:
package test;
import java.util.Arrays;
public class Main {
public static void main(String[] args) throws Exception {
byte[] a = new byte[] { 0xEF - 256, 0xBB - 256, 0xBF - 256 };
byte[] b = new byte[] { 0xFE - 256, 0xFF - 256 };
byte[] c = new byte[] { 0xFF - 256, 0xFE - 256 };
String aString = new String(a, 0, 3, "UTF-8");
String bString = new String(b, 0, 2, "UTF-16BE");
String cString = new String(c, 0, 2, "UTF-16LE");
System.out.println(Arrays.toString(aString.getBytes("UTF-8")));
System.out.println(Arrays.toString(bString.getBytes("UTF-8")));
System.out.println(Arrays.toString(cString.getBytes("UTF-8")));
}
}
输出结果:
[-17, -69, -65]
[-17, -69, -65]
[-17, -69, -65]