先讲个故事
美国人发明了计算机,然后他们觉得计算机也就a-z A-Z 0-9这些字符。
然后他们就发明了ASCII码。一共也就256个,十六进制就是0X00-0XFF
后来各个国家都觉得计算机这东西不错,然后也都想在计算机上显示自己国家的语言。
然后各国就都出现了自己国家的字符集。当然他们也要兼容ASCII码。中国就出现了GBK/GBK2312.
但各个国家的语言都定义了自己的字符集,不兼容怎么办?
然后人们就像发明一个能包含所有语言文字的编码,就出现了UNICODE编码。
“unicode是2个字节。 这一标准的2字节形式通常称作UCS-2。然而,受制于2字节数量的限制,UCS-2只能表示最多65536个字符。Unicode的4字节形式被称为UCS-4或UTF-32,能够定义Unicode的全部扩展,最多可定义100万个以上唯一字符
但是当时人太穷,买不起太大的硬盘,用UCS-4太浪费存储空间。然后又发明了UTF-8,UTF-8可以通过运算与UNICODE互转。而且UTF-8是可变长的。比如A、B、C这些简单的用一个字节就能表示了。
那么现在问题又来了。我们用GBK编码存储的文件怎么显示呢?那么就需要一个对照表,比如有一张表纪录了所有GBK编码表和UNICODE之间的对应关系。当然这个表仅仅纪录了中文。也就是说,GBK里面所有的编码都能对应到UNICODE,但UNICODE的编码未必能在GBK中找到。
结论
UNICODE转UTF-8只需要运算就可以直接互转
UNICODE与GBK转换需要一张编码对应表
屏幕上显示的、键盘上输入的其实都是UNICODE编码的字符串,但是当保存成文件时候就会根据设定的字符集进行转换了。当打开文件时也一样,先要知道文件具体的字符集然后通过对应表(GBK)或者数学运算(UTF-8)转换成UNICODE
UTF-8编码原理
UTF-8采用变长方式存储,也就是要看对应的UNICODE编码是多少来决定采用多少字节表示,下表中X表示可以填入数据的位置
1字节 0xxxxxxx //可填入1-7位
2字节 110xxxxx 10xxxxxx //可填入 8-11位 。1-7用上面的就可以了,没必要浪费空间
3字节 1110xxxx 10xxxxxx 10xxxxxx //可填入12-16位 1-11用上面的就可以了,没必要浪费空间
4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
举个例子
"汉"这个字的UNICODE编码是6C49 二进制01101100 01001001 需要16位 ,那么根据上面的表格可以确定要用3字节表示
将0110 110001001001从左至右填入下面的方块中
1110▢▢▢▢ 10▢▢▢▢▢▢ 10▢▢▢▢▢▢
11100110 10110001 10001001
转换成16进制 E6B189
测试
百度找个unicode在线工具可以试试“汉”转码后是不是 6C49
在用JAVA代码测试一下看看“汉”转UTF-8是不是E6B189,网上也有汉字转UTF-8的工具。可是编码和UNICODE一样。我也不知道为什么。
下面是JAVA代码示例
public class Test1 {
public static String bytesToHexString(byte[] bArr) {
StringBuffer sb = new StringBuffer(bArr.length);
String sTmp;
for (int i = 0; i < bArr.length; i++) {
sTmp = Integer.toHexString(0xFF & bArr[i]);
if (sTmp.length() < 2)
sb.append(0);
sb.append(sTmp.toUpperCase()+" ");
}
return sb.toString();
}
public static void main(String[] arg)
{
try {
System.out.println(Test1.bytesToHexString("汉".getBytes("UTF-8")));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出 :E6 B1 89
下面介绍具体应用
说明UTF8对应汉字通常需要3字节,GBK对应汉字需要2字节
首先要说明String,String就是可见字符,是可以打印到屏幕的,因此他就是转换后的UNICODE码, 没有具体字符集不是GBK也不是UTF-8
先说UTF-8转成GBK,再转会到UTF-8
public class Test1 {
public static void main(String[] arg)
{
try {
String s = "测试";//UNICODE编码
byte[] b = s.getBytes("UTF-8");//转成UTF-8编码 6个字节长 E6 B5 8B E8 AF 95
System.out.println(Test1.bytesToHexString(b));//输出 E6 B5 8B E8 AF 95
String sU2F = new String(b,"GBK");//本来是UTF8编码 非告诉系统这个是GBK编码。系统会两两组合(GBK是双字节存储的)到对照表中找寻对应的字符 (E6 B5) (8B E8) (AF 95)
System.out.println(sU2F);//显示乱码 ,因为对应的位置一定是错的 而且之前的两个汉字变成了3个汉字 显示:娴嬭瘯,但是数据没有丢失,如果获取娴嬭瘯的GBK编码其仍然是E6 B5 8B E8 AF 95
String sF2U = new String(sU2F.getBytes("GBK"),"UTF-8");//重新创建String ,告诉系统 这个byte[] 是UTF-8编码 ,编码正确 字符串显示也就正确了。
System.out.println(sF2U);//输出测试
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] arg)
{
try {
String s = "测试啊";//UNICODE编码
byte[] b = s.getBytes("UTF-8");//转成UTF-8编码 9个字节长度 E6 B5 8B E8 AF 95 E5 95 8A
System.out.println(Test1.bytesToHexString(b));//输出 E6 B5 8B E8 AF 95 E5 95 8A
String sU2F = new String(b,"GBK");//本来是UTF8编码 非告诉系统这个是GBK编码。系统会两两组合(GBK是双字节存储的)去GBK编码表中寻找对应汉字 (E6 B5) (8B E8) (AF 95)(E5 95) (8A XX)
System.out.println(sU2F);//显示乱码 , 而且之前的3个字变成了5个字 而且还引入了后面一个未知字节 显示:娴嬭瘯鍟�
String sF2U = new String(sU2F.getBytes("GBK"),"UTF-8");//重新创建String ,告诉系统 这个byte[] 是UTF-8编码 ,前面都正确,但最后因为引入了未知字节,最后一个字会有问题
System.out.println(sF2U);//输出乱码:测试�?
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
下面介绍GBK转UTF-8
public static void main(String[] arg)
{
try {
String s = "测试";//UNICODE编码
byte[] b = s.getBytes("GBK");//转成GBK编码 4个字节长度 B2 E2 CA D4
System.out.println(Test1.bytesToHexString(b));//输出 B2 E2 CA D4
String sU2F = new String(b,"UTF-8");//本来是GBK编码 非告诉系统这个是UTF-8编码。
System.out.println(Test1.bytesToHexString(sU2F.getBytes("UTF-8")));//EF BF BD EF BF BD EF BF BD EF BF BD
//本来4个字节怎么就变成了12个字节了?
//因为UTF-8是有编码规则的,上面已经介绍过。发现不符合规则 就转成 EF BF BD ,一共4个字节就是12个字节了
System.out.println(sU2F);//显示乱码���� ,EF BF BD 对应UTF-8就是�
//由于此时的编码是没对应上,都强制转换成 EF BF BD 就等于编码已经完全丢失,因此再转换回GBK也是错误的
//之前GBK转UTF-8 是对应到了。虽然是错的,但在对照表中找到了。所以并没有丢失数据。
String sF2U = new String(sU2F.getBytes("UTF-8"),"GBK");
System.out.println(sF2U);//输出乱码:锟斤拷锟斤拷 当把GBK编码的byte当成UTF8处理 大多数情况都是无法对应的。也就是说基本都是 锟斤拷
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
结论
一切byte[] 都要知道他本身的编码 不然基本都会出错,比如 读取文件 接收网络传输的信息时都需要明确知道字符串的编码格式。
一切String 都是UNICODE编码 不是UTF-8 也不是GBK的
如果把GBK编码的byte[] 当成 UTF-8 一定会丢失数据,而且没有办法转回来
如果把UTF-8编码的byte[] 当成GBK 处理 就要看运气了。如果偶数中文还可以转回来,基数中文就会在最后一个汉字引入了未知字节,即使转换回来也一定是乱码