背景:
有一个db链接,使用了latin1编码。有一个latin1的表,里面要存储UTF8或者GBK的中文。
然后在java代码层,为了把正确编码的中文落库,使用了网上流传着的这么一段编码转换的代码
public static String encodeLantin1(String s) {
try {
return new String(s.getBytes("GBK"), "ISO-8859-1");
} catch (Exception e) {
logger.error("转换错误!(可能为空值)");
}
return null;
}
这个代码的功能如下:
输入一个字符串s,将这个字符串编码为GBK,然后返回成latin1(ISO-8859-1)数据库可以存储的值。但是网上同时流传着一个说法:java底层使用UTF16存储字符串,所以不存在一个String是使用UTF8编码,或者GBK编码的。
作为一个c程序员,疑惑就出来了:既然java是使用UTF16存储的,那我转编码后,然后又用String存储,那我存到不就是一样的二进制吗?转编码有意义?或者更直白的,这个转编码过程,到底做了什么?
为了解决疑惑,我简单写了一个测试程序,内容如下
public class Main {
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}
public static void main(String[] args) {
try
{
System.out.println("你好");
System.out.println("direct:"+bytesToHex("你好".getBytes()));
System.out.println("get as utf8:"+bytesToHex("你好".getBytes("UTF-8")));
System.out.println("get as gbk:"+bytesToHex("你好".getBytes("GBK")));
System.out.println("get as iso:"+bytesToHex("你好".getBytes("ISO-8859-1")));
System.out.println("convert to utf8 and then get as iso:"+bytesToHex(new String("你好".getBytes(), "ISO-8859-1").getBytes("ISO-8859-1")));
System.out.println("convert to gbk and then get as iso:"+bytesToHex(new String("你好".getBytes("GBK"), "ISO-8859-1").getBytes("ISO-8859-1")));
System.out.println(new String("你好".getBytes(), "ISO-8859-1"));
}catch(Exception e)
{
}
}
}
输出结果如下
你好
direct:E4BDA0E5A5BD
get as utf8:E4BDA0E5A5BD
get as gbk:C4E3BAC3
get as iso:3F3F
convert to utf8 and then get as iso:E4BDA0E5A5BD
convert to gbk and then get as iso:C4E3BAC3
ä½ å¥½
所以这里传达了几个信息:
1,jvm的默认getbyte编码为utf8
2,使用不同编码进行getbyte,得到的结果不同
3,直接使用ISO-8859-1是不能拿到中文的正确编码的。只能拿到3F3F这个错误的编码(如果在latin1的数据库连接上,直接丢中文”你好“,数据库里得到的也是3F3F)
4,经过上述转换之后的string,使用ISO-8859-1取出来的bytes,跟从原始中文直接取对应的编码的bytes,得到的结果是一致的。
结合java使用UTF16存储文字,我们可以得到下面转换过程。
原始中文,存储java虚拟机的String -> 此时String使用UTF16编码,底层存储为原始中文的UTF16。
使用getBytes,按照UTF8编码取出bytes,中间经历了UTF16->UTF8。此时得到一个bytes数组,是该中文对应的UTF8编码的bytes
使用ISO-8859-1编码,把上面的bytes重新存进String中,此时String里面存的,是使用ISO-8859-1编码的字符,再通过UTF16转换后的字符。
再使用ISO-8859-1编码把bytes拿出来,此时经历了 UTF16 -> ISO-8859-1这个过程。
饶了这么大的圈,其实就是为了解决,某些中文的字符,没有对应的ISO-8859-1编码,直接丢给laitn1的db链接会转码失败这个问题。整个流程如下(左右两个框第一行为字符,第二行为对应的UTF16编码)