字符集原理GBK与UTF8与UNICODE

先从宏观角度分析

最开始操作系统仅仅支持a-z A-Z 0-9还有一些简单符号就行了,因为都是英文系统。

然后各国计算机也发展起来了。那就要支持各国的语言了。各国都编写了自己的字符集,汉字就是GB2312、GBK 。其它国家也有自己的编码

再然后为了统一各国编码就出现了UNICODE,可以通过对应表进行其它语言支持。比如"汉"这个字对应的UNICODE编码是‭66111‬,16进制就是‭6C49‬,GBK中16进制编码是BA BA。那么就需要从6c49对应BABA的表。如果转换成其它字符集也同样需要一个对应的表。这个对应表可能是各种字符集根据UNCODE发布的表来自己出具的把(我瞎猜的)

由于UNICODE存储占用空间大,后来出现了变长存储的UTF-8。与其它字符集不同的是,UNICODE与UTF-8转换不需要对应表。通过算法就可以进行转换。具体百度查查,说明文章很多。下面简单介绍一下:

UTF-8采用变长方式纪录,也就是要看对应的UNICODE编码是多少来决定采用多少字节表示

1字节 0xxxxxxx 
2字节 110xxxxx 10xxxxxx 
3字节 1110xxxx 10xxxxxx 10xxxxxx 
4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 
5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 
6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

X表示可填入的位。 1个自己能填入7位,2字节能填入11位,3字节能填入16位 

比如系统读取到下一个字节高位是0那么说明读取一个字节转换成UNICODE就可以了

如果读取高位是110 那么就要读取两个字节转换成UNICODE,依次类推

"汉"这个字的UNICODE编码是6C49 二进制‭01101100 01001001‬ 需要16位 对应上表,也就是要用3字节表示

所以在GBK与UTF-8转换过程中是需要UNICODE作为中转的,也需要操作系统底层存在对应关系的。

将0110 110001001001从左只有填入下面的方块中

1110▢▢▢▢ 10▢▢▢▢▢▢ 10▢▢▢▢▢▢

11100110 10110001 10001001

转换成16进制 ‭E6B189‬

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 

 

下面介绍具体应用

首先要说明String 没有具体字符集 都是UNICODE编码 

先说UTF-8转成GBK 再转回来

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 {
			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个字  显示:娴嬭瘯
			
			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是双字节存储的)   (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 处理 就要看运气了。如果偶数中文还可以转回来,基数中文就会在最后一个汉字引入了未知字节,即使转换回来也一定是乱码

发布了17 篇原创文章 · 获赞 6 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/yunxiang/article/details/104690729
今日推荐