论坛首页 入门技术论坛

Unicode文件中文编码解码的实现

浏览 4883 次
精华帖 (1) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-07-29  

<!---->

近期开发一个处理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;
    }

今天早上发现temp = s.getBytes("Unicode");在winxp和win2k上面得到的结果是不同的.
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;
}
   发表时间:2008-07-30  
今天早上发现temp = s.getBytes("Unicode");在winxp和win2k上面得到的结果是不同的.
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;
    }
   
0 请登录后投票
   发表时间:2009-06-13  
能提供源码就更好,感激不尽!
0 请登录后投票
论坛首页 入门技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics