字符编码

根据以下链接中的内容总结而成

https://zhuanlan.zhihu.com/p/25435644

http://cenalulu.github.io/linux/character-encoding/

http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

http://blog.itpub.net/55022/viewspace-713901

unicode http://www.chi2ko.com/tool/CJK.htm

GBK http://ff.163.com/newflyff/gbk-list/


字符编码解码

字符集合(Character set):是一组形状的集合,例如所有汉字的集合,发明于公元前,发明者是仓颉。它体现了字符的“形状”,它与计算机、编码等无关。

编码字符集(Coded character set):是一组字符对应的编码(即数字),为字符集合中的每一个字符给予一个数字。例如最早的编码字符集ASCII,发明于1967年。再例如Java使用的unicode,发明于1994年(持续更新中)。由于编码字符集为每一个字符赋予一个数字,因此在java内部,字符可以认为就是一个16位的数字,因此以下方式都可以给字符赋值:

char c=‘中’

char c =0x4e2d

char c=20013

字符编码方案(Character-encoding schema):将字符编码(数字)映射到一个字节数组的方案,因为在磁盘里,所有信息都是以字节的方式存储的。因此Java的16位字符必须转换为一个字节数组才能够存储。例如UTF-8字符编码方案,它可以将一个字符转换为1、2、3或者4个字节。

一般认为,编码字符集和字符编码方案合起来被称之为字符集(Charset),这是一个术语,要和前面的字符集合(Character set)区分开。

字符集合(Character set):是一组形状的集合,一般存储于字库中。

编码字符集(Coded character set):是一组字符对应的编码(即数字),为字符集合中的每一个字符给予一个数字。

字符编码方案(Character-encoding schema):将字符编码(数字)映射到一个字节数组的方案。

字符集(Charset):是编码字符集和字符编码方案的组合。


UTF-8编码简介

为了更好的理解后面的实际应用,我们这里简单的介绍下UTF-8的编码实现方法。即UTF-8的物理存储和Unicode序号的转换关系。 UTF-8编码为变长编码。最小编码单位(code unit)为一个字节。一个字节的前1-3个bit为描述性部分,后面为实际序号部分。

  • 如果一个字节的第一位为0,那么代表当前字符为单字节字符,占用一个字节的空间。0之后的所有部分(7个bit)代表在Unicode中的序号。

  • 如果一个字节以110开头,那么代表当前字符为双字节字符,占用2个字节的空间。110之后的所有部分(5个bit)加上后一个字节的除10外的部分(6个bit)代表在Unicode中的序号。且第二个字节以10开头

  • 如果一个字节以1110开头,那么代表当前字符为三字节字符,占用3个字节的空间。110之后的所有部分(5个bit)加上后两个字节的除10外的部分(12个bit)代表在Unicode中的序号。且第二、第三个字节以10开头

  • 如果一个字节以10开头,那么代表当前字节为多字节字符的第二个字节。10之后的所有部分(6个bit)和之前的部分一同组成在Unicode中的序号。

具体每个字节的特征可见下表,其中x代表序号部分,把各个字节中的所有x部分拼接在一起就组成了在Unicode字库中的序号

Byte 1Byte 2Byte3
0xxx xxxx
110x xxxx10xx xxxx
1110 xxxx10xx xxxx10xx xxxx

我们分别看三个从一个字节到三个字节的UTF-8编码例子:

实际字符在Unicode字库序号的十六进制在Unicode字库序号的二进制UTF-8编码后的二进制UTF-8编码后的十六进制
$0024010 01000010 010024
¢00A2000 1010 00101100 0010 1010 0010C2 A2
20AC0010 0000 1010 11001110 0010 1000 0010 1010 1100E2 82 AC

从以上的简单介绍中得出以下规律:

  • 3个字节的UTF-8十六进制编码一定是以E开头的

  • 2个字节的UTF-8十六进制编码一定是以CD开头的

  • 1个字节的UTF-8十六进制编码一定是以比8小的数字开头的

Unicode符号范围 (十六进制)UTF-8编码方式(二进制)
0000 0000-0000 007F0xxxxxxx
0000 0080-0000 07FF110xxxxx 10xxxxxx
0000 0800-0000 FFFF1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

跟据上表,解读 UTF-8 编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。这里的关系是,UTF-8 是 Unicode 的实现方式之一。


Unicode的全称是“Universal Multiple-Octet Coded Character Set”,通用多字节编码字符集,简写为UCS。 Unicode规定了一组字符对应的编码。恰好这组字符就是全人类目前所有的字符。 UCS-2是指用两个字节对应一个字符的编码字符集;UCS-4则是指用四个字节对应一个字符的编码字符集。你可以认为,目前为止Unicode有两个具体的编码字符集,UCS-2和UCS-4。 Unicode是某种编码字符集(目前包括UCS-2和UCS-4两种),而UTF则是字符编码方案,就是将字符编码(数字)映射到一个字节数组的方案。UTF中的U是指Unicode,也就是将Unicode编码映射到字节数组的方案。目前UTF包括UTF-7、UTF-8、UTF-16和UTF-32,后面的数字代表转换时最小的位数。例如UTF-8就是用几个8位二进制数来代表一个Unicode编码。而UTF-16就是用几个16位二进制数来代表一个Unicode编码。

Big Endian和Little Endian是字节序字节序就是数据在内存中存放的顺序,多于一个字节的数据在内存中存放时有两种选择,即Big Endian和Little Endian。 Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。 Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。 Big Endian和Little Endian和芯片类型以及操作系统都有关系。但是由于Java是平台无关的,所以Java被设计为Big Endian的。但是当Java中的字符进行编码时,就要注意其字节序了。 例如UTF-16字符编码方案就分为UTF-16BE和UTF-16LE。Unicode 规范定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做"零宽度非换行空格"(zero width no-break space),用FEFF表示。这正好是两个字节,而且FF比FE大1。如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。


GBK

GBK是一个字符集GBK同时包含编码字符集和字符编码方案。GBK的中文编码是双字节来表示的,英文编码是用ASCII码表示的,用单字节表示。但GBK编码表中也有英文字符的双字节表示形式,所以英文字母可以有2中GBK表示方式。为区分中文,将其最高位都定成1。英文单字节最高位都为0。当用GBK解码时,若高字节最高位为0,则用ASCII码表解码;若高字节最高位为1,则用GBK编码表解码GBK编码是GB2312编码的超集,向下完全兼容GB2312,同时GBK收录了Unicode基本多文种平面中的所有CJK汉字。同 GB2312一样,GBK也支持希腊字母、日文假名字母、俄语字母等字符,但不支持韩语中的表音字符(非汉字字符)。GBK还收录了GB2312不包含的汉字部首符号、竖排标点符号等字符。

GBK的整体编码范围是为0x8140-0xFEFE,不包括低字节是0×7F的组合。高字节范围是0×81-0xFE,低字节范围是0x40-7E和0x80-0xFE。

低字节是0x40-0x7E的GBK字符有一定特殊性,因为这些字符占用了ASCII码的位置,这样会给一些系统带来麻烦。 有些系统中用0x40-0x7E中的字符(如“|”)做特殊符号,在定位这些符号时又没有判断这些符号是不是属于某个 GBK字符的低字节,这样就会造成错误判断。在支持GB2312的环境下就不存在这个问题。需要注意的是支持GBK的环境中小于0x80的某个字节未必就是ASCII符号;另外就是最好选用小于0×40的ASCII符号做一些特殊符号,这样就可以快速定位,且不用担心是某个汉字的另一半。Big5编码中也存在相应问题


GBK、GB2312等与UTF8之间都必须通过Unicode编码才能相互转换:

GBK、GB2312--Unicode--UTF8

UTF8--Unicode--GBK、GB2312

GBK,GB2312以及Unicode都既是字符集,也是编码方式,而UTF-8只是编码方式,并不是字符集GBK编码中英文字符只占一个字节


例:严

1)ANSI:文件的编码就是两个字节D1 CF,这正是严的 GB2312 编码,这也暗示 GB2312 是采用大头方式存储的。

2)Unicode:编码是四个字节FF FE 25 4E,其中FF FE表明是小头方式存储,真正的编码是4E25。

3)Unicode big endian:编码是四个字节FE FF 4E 25,其中FE FF表明是大头方式存储。

4)UTF-8:编码是六个字节EF BB BF E4 B8 A5,前三个字节EF BB BF表示这是UTF-8编码,后三个E4B8A5就是严的具体编码,它的存储顺序与编码顺序是一致的。

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Properties;

public class codeText {

    //从数字到字节数组—编码
    private static byte[] encoding1(String str, String charset) throws UnsupportedEncodingException {
        return str.getBytes(charset);
    }

    private static byte[] encoding2(String str, String charset) {
        Charset cset = Charset.forName(charset);
        ByteBuffer byteBuffer = cset.encode(str);
        byte[] bytes = new byte[byteBuffer.remaining()];
        byteBuffer.get(bytes);
        return bytes;
    }

    //从字节数组到数字—解码
    private static String decoding1(byte[] bytes,String charset) throws UnsupportedEncodingException {
        String str = new String(bytes, charset);
        return str;
    }
    private static String decoding2(byte[] bytes, String charset) {
        Charset cset = Charset.forName(charset);
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        CharBuffer charBuffer = cset.decode(buffer);
        return charBuffer.toString();
    }

    /**
     Java的默认字符集,可以在两个地方设定,一是执行java程序时使用-Dfile.encoding参数指定,例如-Dfile.encoding=UTF-8就指定默认字符集是UTF-8。二是在程序执行时使用Properties进行指定,如下:

     注意,这两种方法如果同时使用,则程序开始时使用参数指定的字符集,在Properties方法后使用Properties指定的字符集。
     如果这两种方法都没有使用,则使用操作系统默认的字符集。例如中文版windows 7的默认字符集是GBK。
     默认字符集的优先级如下:
     1.程序执行时使用Properties指定的字符集;
     2.java命令的-Dfile.encoding参数指定的字符集;
     3.操作系统默认的字符集;
     4.JDK中默认的字符集,我跟踪了JDK1.8的源代码,发现其默认字符集指定为ISO-8859-1。
     */
    private static void setEncoding(String charset) {
        Properties properties = System.getProperties();
       // properties.put("file.encoding",charset);
        System.out.println(properties.get("file.encoding"));
    }
    private static String getEncoding() {
        Properties properties = System.getProperties();
        System.out.println(properties.get("file.encoding"));
        return properties.get("file.encoding").toString();
    }

    //JDK支持的字符集
    private static void printAvailableCharsets() {
        Map<String ,Charset> map = Charset.availableCharsets();
        System.out.println("the available Charsets supported by jdk:"+map.size());
        for (Map.Entry<String, Charset> entry :
                map.entrySet()) {
            System.out.println(entry.getKey());
        }
    }

    private static void generateGrabledCode() throws UnsupportedEncodingException {
        String str = "中国";
        byte[] bytes = str.getBytes("UTF-8");
        str = new String(bytes, "GBK");
        System.out.println(str);
    }

    public static void main(String[] args){
       // printAvailableCharsets();
        //getEncoding();
        /**
         中 unicode:4e2d; GBK:d6d0
         默认UTF-8:11100100 10111000 10101101
         UTF-8: 11100100 10111000 10101101
         Unicode: 11111111111111111111111111111110 11111111111111111111111111111111 1001110 101101 ;fe ff表示Big Endian大头方式存储
         GBK:11010110 11010000
         */
        String str = "中";
        try {
            byte[] bytes = str.getBytes();
            for(byte b:bytes){
                System.out.print(Integer.toBinaryString(b).substring(24)+" ");
            }
            System.out.println();
            byte[] bytesUTF = str.getBytes("UTF-8");
            for(byte b:bytesUTF){
                System.out.print(Integer.toBinaryString(b).substring(24)+" ");
            }
            System.out.println();
            byte[] bytesUnicode = str.getBytes("Unicode");
            for(byte b:bytesUnicode){
                System.out.print(Integer.toBinaryString(b)+" ");
            }
            System.out.println();
            byte[] bytesGBK = str.getBytes("GBK");
            for(byte b:bytesGBK){
                System.out.print(Integer.toBinaryString(b).substring(24)+" ");
            }
            System.out.println();
            str = new String(bytesGBK, "GBK");
            System.out.println(str);
        }catch (UnsupportedEncodingException e){
            e.printStackTrace();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值