编码问题之转码

这几天遇到编码问题,初步整理一下。

计算机中有好多编码,编码问题也是因为文字的编码不统一,编码间的差异造成的。

所谓乱码,其实就是因为字符使用的编码,解码的类型不一样,才出现的一些不认识或者不是想要的数据。

比如:用UTF-8编码的【中国】 换成GBK 解码时会出现【涓湅】;换成GB2312 解码时【涓��】……


一、那我们首先看看有多少编码类型:

1、ASCII 码 :

一共有128种字符编码,是对英文字符(字母+符号)和二进制统一的编码,计算机里是用二进制位进行通信的,所以这一套ASCII 码使英文字符在计算机里基本上可以直接使用了,着实给使用英语为母语的国家带来了很大的方便。

2、Unicode 编码:

Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。而且Unicode 有多种存储方式,使用起来不是很方便。

其实Unicode 对我们判断使用汉语的国家人民还有另一种不便,我们如果判断汉语是不是繁体 或是简体的话 其实就很麻烦了,因为Unicode 存储汉语是有一定的范围的,可是繁简体之间是混插的 没有界限的,所以需要自己转码进行辨别。

3、UTF-8:

UTF-8 其实是Unicode 的一种实现方式(还有UTF-16等等)。在互联网中使用的非常广。因为UTF-8 编码可以使整个互联网中实现同意编码,尽量的减少或避免因为编码的差异造成的不必要的麻烦。

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8的编码规则:
1)如果是 单字节的符号,那么 令字节的第一位设为0,后面7位为这个符号的unicode码。对于英语字母来说,UTF-8编码和ASCII码是相同的。
2)如果是 n 字节的符号(n>1),第一个字节的前面的 n位都设为1,第n+1位设为0,后面的字节的前两位均为10。其他的二进制位是由这个字符的 Unicode 码 按照顺序进行填充而得。
如下表:
            Unicode符号范围 | UTF-8编码方式
                      (十六进制)   | (二进制)
----------------------------------+---------------------------------------------
0000 0000-0000 007F    |      0xxxxxxx
0000 0080-0000 07FF    |     110xxxxx 10xxxxxx
0000 0800-0000 FFFF    |     1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF    |     11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 

4、ISO-8859-1 :

因为除了应该还有其他语言,尤其是西欧 比如法语 会有重音字符,这就造就了ISO-8859-1 编码,ISO-8859-1 是国际标准化组织内ISO/IEC 8859的第一个8位字符集,它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入96个字母及符号,借以供使用变音符号的拉丁字母语言使用。所以又称Latin-1或“西欧语言”。

5、GB2313 ,Big5,GBK

这几种是正对使用汉字或者类汉字的字符的编码方式。

①GB2312 码 :

兼容ASCII 码  共收入汉字6763个和非汉字图形字符682个。整个字符集分成94个区,每区有94个位。每个区位上只有一个字符,因此可用所在的区和位来对汉字进行编码,称为区位码。其中主要是简体中文 和 部分繁体中文及日韩的一些汉字。

②Big5 (大5码):

主要是港澳台使用的繁体字的编码集。

③GBK :

它是GB2312的扩展,加入对繁体字的支持,兼容GB2312。字符有一字节和双字节编码,00–7F范围内是一位,和ASCII保持一致,此范围内严格上说有96个文字和32个控制符号。之后的双字节中,前一字节是双字节的第一位。总体上说第一字节的范围是81–FE(也就是不含80和FF),第二字节的一部分领域在40–FE,其他领域在80–FE。

6、其他的不常用 不再介绍

二、编码之间的转换:

如果字符用一种编码方式编码,再用同一样的编码方式解码,那么输出的字符是正常的;要是解码时用的是另一中编码则会出现乱码。

那么我们用代码试试:

public static void main (String[] args) throws java.lang.Exception{
       String  str = new String("我".getBytes("UTF-8"),"GB2312");
       String  str2 = new String("我".getBytes("UTF-8"),"UTF-8"); 
       System.out.println("str: "+str+"  "+"  str2: "+str2);
	}
执行的结果:

str: ��    str2: 我


这结果可以有力证明乱码的出现 和 其解决办法。

三、分辨编码范围:

若只判断中文字符时可以 判断是不是属于 CJK(中日韩统一表意文字):

public static boolean isChinese(char c) {
        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
        if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
                || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
                || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
                || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION
                || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
                || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) {
            return true;
        }
        return false;
    }

那么判断文简体还是繁体呢?  我觉得还是得判断编码范围了:

思路是 当文字是在GB2312 范围内 时我们认为是简体中文 ,若在Big5 范围内时 是繁体。如下:

 private static final String gbEncode = "GB2312";
 private static final String big5Encode = "Big5";

    public static boolean isJTChinese(Character c) {
        String str = c.toString();
        try {
            if (str.equals(new String(str.getBytes(gbEncode), gbEncode))) {
                System.out.println(c+"是简体");
                return true;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return false;
    }

    public static boolean isFTChinese(Character c) {
        String str = c.toString();
        try {
            if (str.equals(new String(str.getBytes(big5Encode), big5Encode))) {
                System.out.println(c+"是繁体");
                return true;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return false;
    }

其他的问题同理可以得到答案。

四、第三方库

现成的一个库可以使用 :language-detection。      language-detection 项目链接  https://code.google.com/p/language-detection/

此工具是一个使用java实现的语言检测工具,使用的是朴素贝叶斯方法,对噪声少的语言句子,准确率能达到99%,目前已经能够支持53种语言。

使用非常方便,只要把下载下来的2个jar 导入工程,然后把含有语言包的 profiles 属性文件加载进去即可使用。

测试代码如下:

import com.cybozu.labs.langdetect.Detector;
import com.cybozu.labs.langdetect.DetectorFactory;
import com.cybozu.labs.langdetect.LangDetectException;

public class LangTest {

    public static void main(String[] args) {
        Detector detect;
        try {
            //加载语言包
            DetectorFactory.loadProfile(Thread.currentThread().getContextClassLoader().getResource("profiles").getPath());
            detect = DetectorFactory.create();
            detect.append("我是软件工程师。");
            System.out.println(detect.detect());
        } catch (LangDetectException e) {
            e.printStackTrace();
        }
    }

}

输出的j结果是:zh-cn


好吧,编码之间的一些转码,语言辨别的问题,暂时先学到这,以后还需继续学习。

文章中有什么不足的还需看官不吝赐教……

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