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)方法,需要把这个偏移也减掉。如果不用于这种场合,可以酌情保留或者去掉。