字符集原理GBK与UTF8与UNICODE

先讲个故事

美国人发明了计算机,然后他们觉得计算机也就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 处理 就要看运气了。如果偶数中文还可以转回来,基数中文就会在最后一个汉字引入了未知字节,即使转换回来也一定是乱码

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章