<!---->
近期开发一个处理WMA 文件的公用类库,发现最头疼的问题不是WMA编码格式的问题而是Unicode这种文件格式的问题.经过近两天的研究,终于解决了这一问题,并且在今天下午基本完成了WMA文件解析处理类库的开发.利用余下的一点时间偷偷将Unicode相关的内容整理一下,希望我的老板不要发现这件事情.o(∩_∩)o...哈哈
字节流的字符编码:
字符编码把字符转换成数字存储到计算机中,按 ASCii 将字母映射为整数。
把数字从计算机转换成相应的字符的过程称为解码。
乱码的根源在于编解码方式不统一。在世界上任何一种编码方式中都会向上兼容 ASCII 码。所以英文没有乱码。
编码方式的分类:
ASCII (数字、英文) :1 个字符占一个字节(所有的编码集都兼容 ASCII )
ISO8859-1 (欧洲): 1 个字符占一个字节
GB-2312/GBK : 1 个字符占两个字节。 GB 代表国家标准。
GBK 是在 GB - 2312 上增加的一类新的编码方式,也是现在最常用的汉字编码方式。
Unicode: 1 个字符占两个字节(网络传输速度慢)
UTF-8 :变长字节,对于英文一个字节,对于汉字两个或三个字节。
原则:保证编解码方式的统一,才能不至于出现错误。
<!----> <!---->
UTF 的字节序和 Byte Order Mark
UTF-8 以字节为编码单元,没有字节序的问题。 UTF-16 以两个字节为编码单元,在解释一个 UTF-16 文本前,首先要弄清楚每个编码单元的字节序。例如收到一个 “ 奎 ” 的 Unicode 编码是 594E , “ 乙 ” 的 Unicode 编码是 4E59 。如果我们收到 UTF-16 字节流 “594E” ,那么这是 “ 奎 ” 还是 “ 乙 ” ?
<!----> <!---->
Unicode 规范中推荐的标记字节顺序的方法是 Byte Order Mark 。而是 Byte Order Mark 。在 UCS 编码中有一个叫做 "ZERO WIDTH NO-BREAK SPACE" 的字符,它的编码是 FEFF 。而 FFFE 在 UCS 中是不存在的字符,所以不应该出现在实际传输中。 UCS 规范建议我们在传输字节流前,先传输字符 "ZERO WIDTH NO-BREAK SPACE" 。
这样如果接收者收到 FEFF ,就表明这个字节流是 Big-Endian 的;如果收到 FFFE ,就表明这个字节流是 Little-Endian 的。
<!----> <!---->
UTF-8 不需要 Byte Order Mark 来表明字节顺序,但可以用 Byte Order Mark 来表明编码方式。字符 "ZERO WIDTH NO-BREAK SPACE" 的 UTF-8 编码是 EF BB BF 。所以如果接收者收到以 EF BB BF 开头的字节流,就知道这是 UTF-8 编码了。
如同样是字符 "A" ﹐在以下几种格式中的存储形式分别是﹕
UTF-16 big-endian : 00 41
UTF-16 little-endian : 41 00
UTF-32 big-endian : 00 00 00 41
UTF-32 little-endian : 41 00 00 00
<!----> <!---->
因此在一段字节流开始时﹐如果接收到以下字节﹐则分别表明了该文本文件的编码。
UTF-8: EF BB BF
UTF-16 : FF FE
UTF-16 big-endian: FE FF
UTF-32 little-endian: FF FE 00 00
UTF-32 big-endian: 00 00 FE FF
而如果不是以这个开头﹐那程序则会以 ANSI, 也就是系统默认编码读取。
<!----> <!---->
ANSI 文件格式的字节流﹕
BA-BA-41
10111010 10111010 01000001
Unicode 文件格式的字节流﹕
FF-FE-49-6C-41-00
11111111 11111110 01001001 01101100 01000001 00000000
Unicode-big-endian 文件格式的字节流﹕
FE-FF-6C-49-00-41
11111110 11111111 01101100 01001001 00000000 01000001
utf-8 文件格式的字节流﹕
EF-BB-BF-E6-B1-89-41
11101111 10111011 10111111 11100110 10110001 10001001 01000001
<!----> <!---->
<!----> <!---->
解码
<!----> <!---->
处理 WMA 文件时 , 例如读到一首歌曲的 title 名称为 ”2046 沧海一声笑 ”
使用 byte[] 直接在文件中读到的
[50, 0, 48, 0, 52, 0, 54, 0, -89, 108, 119, 109, 0, 78, -16, 88, 17, 123]
50, 0 对应 2
48, 0 对应 0
52, 0 对应 4
54, 0 对应 6
-89, 108 对应 沧
119, 109 对应 海
0, 78 对应 一
-16, 88 对应 声
17, 123 对应 笑
<!----> <!---->
readUnicodeByte2Hex 的代码实现Unicode字节到16进制码的转换
public static String readUnicodeByte2Hex(byte[] temp) throws IOException {
StringWriter sw = new StringWriter();
for (int index = 0; index < temp.length; index++) {
if (temp[index] > 0xf && temp[index] <= 0xff) {
sw.write(Integer.toHexString(temp[index]));
} else if (temp[index] >= 0x0 && temp[index] <= 0xf) {// 对于只有1位的16进制数前边补“0”
sw.write("0" + Integer.toHexString(temp[index]));
} else {
// 对于int<0的位转化为16进制的特殊处理,因为Java没有Unsigned
// int,所以这个int可能为负数
sw.write(Integer.toHexString(temp[index]).substring(6));
}
}
return sw.toString();
}
<!----> <!---->
转化为 16 进制
3200 3000 3400 3600 a76c 776d 004e f058 117b
2 0 4 6 沧 海 一 声 笑
<!----> <!---->
<!----> <!---->
System.out.println((char) Integer.parseInt("a76c", 16));
输出 : 乱码 ?.
于是 考虑到可能是 Byte Order Mark 的原因 , 尝试将沧字的 16 进制码的高低位进行交换
System.out.println((char) Integer.parseInt("6c a7", 16));
输出 : 沧
decodeHexToChinese的代码实现
public static String decodeHexToChinese(String hex) {
StringBuffer sb=new StringBuffer("");
for(int i=0;i<hex.length();i=i+4){
String temp=hex.substring(i, i+4);
if(temp.equalsIgnoreCase("0000"))
break;
String temp2=""+temp.charAt(2)+temp.charAt(3)+temp.charAt(0)+temp.charAt(1);
char t=(char) Integer.parseInt(temp2, 16);
sb.append(t);
}
return sb.toString();
}
<!----> <!---->
编码
<!----> <!---->
例如尝试将 ”2046 沧海一声笑 ” 写入 WMA 文件
将字符串转换为 hex 的字符串 , 然后转化为 byte 数组
writeNew2Buff
public static void writeNew2Buff(String title,byte[] titleBuff ) throws NumberFormatException,
IOException {
String titelHex=toHexString(title);
int t=0;
//将16进制字符串转化为byte格式
for(int i=0;i<titelHex.length();i=i+2){
titleBuff[t++]=(byte) Integer.parseInt(titelHex.substring(i, i + 2), 16);
}
}
toHexString 将字符串转化为符合Unicode格式规范的16进制字符串
public static String toHexString(String s){
StringBuffer sb=new StringBuffer("");
byte[] temp;
try {
temp = s.getBytes("Unicode");
for(int i=2;i<temp.length;i+=2){
if((temp[i]&0xff)<16){
sb.append("0"+Integer.toHexString(temp[i]&0xff));
}else{
sb.append(Integer.toHexString(temp[i]&0xff));
}
if((temp[i+1]&0xff)<16){
sb.append("0"+Integer.toHexString(temp[i+1]&0xff));
}else{
sb.append(Integer.toHexString(temp[i+1]&0xff));
}
}
return sb.toString();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return s;
}
编码为
[50, 0, 48, 0, 52, 0, 54, 0, -89, 108, 119, 109, 0, 78, -16, 88, 17, 123]
<!----> <!---->
对于写入数字的特殊处理
由于数字存在精度的问题 , 因此 unicode 的文件中数字的精度有 2byte,4byte,8byte 的差别
因此写入数字的时候要考虑位数的差别问题 . 而且空白位要使用 0 进行补足 .
下面给出int转化为2byte和4byte的技术实现.
<!---->
public static byte[] intTo2Bytes(int s) {
byte[] buf = new byte[2];
int pos;
for (pos = 0; pos < 2; pos++) {
buf[pos] = (byte) (s & 0xff);
s >>= 8;
if (s == 0)
break;
}
return buf;
}
public static byte[] intTo4Bytes(int i) {
byte[] arrB = new byte[4];
arrB[3] = (byte) (i >> 24);
arrB[2] = (byte) (i >> 16);
arrB[1] = (byte) (i >> 8);
arrB[0] = (byte) i;
return arrB;
}
winxp和win2k上面对字符串转Unicode的Byte Order Mark 是不同的,不过可以通过
byte数组的前两位进行判断,通过判断ffef还是efff来辨别是big-endian或是litte-endian.toHexString方法代码更新如下
public static String toHexString(String s){
StringBuffer sb=new StringBuffer("");
byte[] temp;
try {
temp = s.getBytes("Unicode");
if(-1==temp[0]&&-2==temp[1]){
for(int i=2;i<temp.length;i+=2){
if((temp[i]&0xff)<16){
sb.append("0"+Integer.toHexString(temp[i]&0xff));
}else{
sb.append(Integer.toHexString(temp[i]&0xff));
}
if((temp[i+1]&0xff)<16){
sb.append("0"+Integer.toHexString(temp[i+1]&0xff));
}else{
sb.append(Integer.toHexString(temp[i+1]&0xff));
}
}
}else
if(-2==temp[0]&&-1==temp[1]){
for(int i=2;i<temp.length;i+=2){
if((temp[i+1]&0xff)<16){
sb.append("0"+Integer.toHexString(temp[i+1]&0xff));
}else{
sb.append(Integer.toHexString(temp[i+1]&0xff));
}
if((temp[i]&0xff)<16){
sb.append("0"+Integer.toHexString(temp[i]&0xff));
}else{
sb.append(Integer.toHexString(temp[i]&0xff));
}
}
}
return sb.toString();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return s;
}