Java 中的字符编码,以及和编码相关的字符串处理

字符串处理经常会遇到编码的问题(汉字、日语、韩语等)。近日遇到一个问题:在JEditorPane中,已知字节数据(UTF-8编码)偏移量,需要定位光标到相应的字符。结果老是有偏差。于是对字符串进行了分析,记录如下:

字符串存储,以及编码转换分析

测试字符串:  String ls="中文测试abcd";

逐个打印每个字符的编码值:

        for (int i =0 ;i < ls.length();i++)
            System.out.print(String.format("%x ",(int)ls.charAt(i) ));

结果:4e2d 6587 6d4b 8bd5 61 62 63 64

可见字符串在Java内部的存储,汉字码值是UniCode编码。

 

然后将字符串分别按不同的编码转换成byte[],再按16进制打印出来,然后再将字节数组还原为字符串:

>>>null Hex:   (这个没有指定编码,直接用getBytes() )
d6 d0 ce c4 b2 e2 ca d4
61 62 63 64
    ReBuilt String: 中文测试abcd


>>>ISO-8859-1 Hex:   
3f 3f 3f 3f 61 62 63 64
    ReBuilt String: ????abcd


>>>GBK Hex:   
d6 d0 ce c4 b2 e2 ca d4
61 62 63 64
    ReBuilt String: 中文测试abcd


>>>GB2312 Hex:   
d6 d0 ce c4 b2 e2 ca d4
61 62 63 64
    ReBuilt String: 中文测试abcd


>>>UTF-8 Hex:   
e4 b8 ad e6 96 87 e6 b5
8b e8 af 95 61 62 63 64
    ReBuilt String: 中文测试abcd


>>>UNICODE Hex:    (这个带有fe ff的前导标记)
fe ff 4e 2d 65 87 6d 4b
8b d5 0 61 0 62 0 63
0 64
    ReBuilt String: 中文测试abcd

以上情况中,除了ISO-8859-1编码无法还原外,其他都可以。

其中,不指定编码,或者指定为GB2312, GBK,getBytes()结果都是汉字内码,而指定Unicode结果就是汉字的Unicode编码,指定UTF-8,结果是对内部的Unicode又按照UTF-8的规则进行了转换。

 

UTF-8编码规则:

0000 – 007F(最多7bit):不变

0080 – 07FF(8bit ~ 11bit)  -->   110xxxxx 10xxxxxx (Bit)

0800 – FFFF(12bit ~ 16bit) -->  1110xxxx 10xxxxxx 10xxxxxx

 

偏移调整方法:

从字符串偏移到字节偏移:

            int strLen = ls.getText().length();
            int byteLen = strLen;
            for (int i = 0;i< strLen ;i++ ){
                char ch = lsTxt.charAt(i);
                if ( ch > 0x7F && ch <= 0x7FF){
                    byteLen += 1; //
                }else if (ch> 0x7FF)
                    byteLen += 2;
            }

该算法从字符串长度计算出了转换为UTF-8编码后的字节数组长度。经验证是正确的。

从字节偏移到字符串偏移

稍微麻烦点,如下:

            int strOffset = byteOffset; //字符串偏移初始化为字节偏移。
            int d_count= 0;
            int t_count =0;
            int cr_count = 0;
            for (int i = 0;i< offset ;i++ ){
                int b = 0x00FF & xmlBytes[i];
                if ( b >= 0x00C0 && b < 0x00E0){ //1100 0000 ~ 1110 0000
                    strOffset -= 1; //
                    i++;
                    d_count++;
                }else if (b >= 0x00E0){
                    strOffset -= 2;
                    i+=2;
                    t_count++;
                }else if (b==10){ //换行
                    strOffset—;   

                    cr_count++;
                }
            }

其中d_count,t_count,cr_count分别是双字节,三字节和回车的计数,仅用于调试,无任何逻辑意义。

之所以换行也要把strOffset减一,是因为要调用JEditorPane的.select(strOffset, strOffset + len)方法,需要把这个偏移也减掉。如果不用于这种场合,可以酌情保留或者去掉。
posted on 2012-05-29 11:18  nullpointer  阅读(266)  评论(0)    收藏  举报