1 什么是BOM?
BOM应用在文件交换场景而非封闭的文件编辑场景。
在UTF-16,BOM在文件流的开头,
(1) BOM =”0xFEFF” – big-endian
(2) BOM =”0xFFFE” –small-endian
UTF-8的BOM(0xEF BB BF)允许但并不建议使用, UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符”ZERO WIDTH NO-BREAK SPACE”的UTF-8编码是EF BB BF。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
Windows就是使用BOM来标记文本文件的编码方式的。用记事本编写代码要注意BOM。
2 BOM的解析
IANA注册的字符集UTF-16BE,UTF-16LE,UTF-32BE,UTF-32LE 不能使用BOM,开头的BOM被解析为”zero-width no-break space”
Encoding |
Representation (hexadecimal) |
Representation (decimal) |
EF BB BF[t 1] |
239 187 191 |
|
FE FF |
254 255 |
|
FF FE |
255 254 |
|
UTF-32(BE) |
00 00 FE FF |
0 0 254 255 |
UTF-32(LE) |
FF FE 00 00 |
255 254 0 0 |
2B 2F 76, and one of the following bytes: [ 38 | 39 | 2B | 2F ][t 2] |
43 47 118, and one of the following bytes: [ 56 | 57 | 43 | 47 ] |
|
F7 64 4C |
247 100 76 |
|
DD 73 66 73 |
221 115 102 115 |
|
0E FE FF[t 3] |
14 254 255 |
|
FB EE 28 optionally followed by FF[t 4] |
251 238 40 optionally followed by 255 |
|
84 31 95 33 |
132 49 149 51 |
3 从ASCII、 GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码 中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030都属于双字节字符集 (DBCS)
4 Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。
5 UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16
6
UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:
UCS-2编码(16进制) |
UTF-8 字节流(二进制) |
0000 - 007F |
0xxxxxxx |
0080 - 07FF |
110xxxxx 10xxxxxx |
0800 - FFFF |
1110xxxx 10xxxxxx 10xxxxxx |
例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001,用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
7 根据RFC2781 的3.2节,Unicode标准建议开始位置的0xFEFF在处理字符内容之前摘除,这是因为开始位置的这个字符可能是编码格式,而非一个”zero with non-breaking space”,应该注意的是,这样一个操作可能会影响外部依赖于所有的字符都完好存在的那些层次的处理(例如是数字签名,或者字节数计算)。当两端UTF-16文字拼接时候,必须先把0xFEFF去掉,避免多余的”zero with non-breaking space”
8 用BOM来测探文件编码类型的一段代码
package util;
/**
*@(#)Detector.java
* 版权声明 , 版权所有 违者必究
* 版本号 1.0
*修订记录:
*1)更改者:李培
* 时 间:2009-12-23
* 描 述:创建
*/
public class Detector {
public Object[] getEncodingName(byte[] aData, int aCount) throws Exception {
if (aCount < 2)
throw new Exception("unknown type");
//utf-16 with BOM
int sB0 = aData[0] & 0xFF;
int sB1 = aData[1] & 0xFF;
if (sB0 == 0xFF && sB1 == 0xFE)
return new Object[] {"UTF-16LE", new Integer(2)};
else if (sB0 == 0xFE && sB1 == 0xFF)
return new Object[] {"UTF-16BE", new Integer(2)};
if (aCount < 3)
throw new Exception("unknown type");
//utf-8 with BOM
int sB2 = aData[2] & 0xFF;
if (sB0 == 0xEF && sB1 == 0xBB && sB2 == 0xBF)
return new Object[] {"UTF-8", new Integer(3)};
if (aCount < 4)
throw new Exception("unknown type");
//other types
int sB3 = aData[3] & 0xFF;
if (sB0 == 0x00 && sB1 == 0x00 && sB2 == 0x00 && sB3 == 0x3C) {
// UCS-4, big endian (1234)
return new Object[] {"ISO-10646-UCS-4", new Integer(4)};
}
if (sB0 == 0x3C && sB1 == 0x00 && sB2 == 0x00 && sB3 == 0x00) {
// UCS-4, little endian (4321)
return new Object[] {"ISO-10646-UCS-4", new Integer(4)};
}
if (sB0 == 0x00 && sB1 == 0x00 && sB2 == 0x3C && sB3 == 0x00) {
// UCS-4, unusual octet order (2143)
// REVISIT: What should this be?
return new Object[] {"ISO-10646-UCS-4", new Integer(4)};
}
if (sB0 == 0x00 && sB1 == 0x3C && sB2 == 0x00 && sB3 == 0x00) {
// UCS-4, unusual octect order (3412)
// REVISIT: What should this be?
return new Object[] {"ISO-10646-UCS-4", new Integer(4)};
}
if (sB0 == 0x00 && sB1 == 0x3C && sB2 == 0x00 && sB3 == 0x3F) {
// UTF-16, big-endian, no BOM
// (or could turn out to be UCS-2...
// REVISIT: What should this be?
return new Object[] {"UTF-16BE", new Integer(4)};
}
if (sB0 == 0x3C && sB1 == 0x00 && sB2 == 0x3F && sB3 == 0x00) {
// UTF-16, little-endian, no BOM
// (or could turn out to be UCS-2...
return new Object[] {"UTF-16LE", new Integer(4)};
}
if (sB0 == 0x4C && sB1 == 0x6F && sB2 == 0xA7 && sB3 == 0x94) {
// EBCDIC
// a la xerces1, return CP037 instead of EBCDIC here
return new Object[] {"CP037", new Integer(4)};
}
throw new Exception("unknow");
}
}