java中的字符编码简介

来源:互联网 发布:树莓派机器人和python 编辑:程序博客网 时间:2024/05/01 23:12

计算机常见编码

一、有关编码的基础知识

1、计算机存储的容量

     位(比特)    bit    最小的单元

     字节 byte     机器语言的单位(计算机存储设备容量的最基本单位)

     1byte = 8bits    8个二进制位为1个字节

     1KB = 1024byte

     1MB = 1024KB

     1GB = 1024MB

     1TB = 1024GB

    字节:只有字节才有地址的概念,对一种计算机的存储设备以字节为单位赋予的地址称为字节编址。一个存储单元可以存储一个字节。

    例如:一个1KB的存储器,他有1024个存储单元,他的编号从0-1023    

2、进制

   二进制:采用0和1两个数码来表示的数。进位规则是“逢二进一”

   八进制:采用0-7八个数字计数,进位规则是“逢八进一”

   十进制:采用0-9十个数字计数,进位规则是“逢十进一”

   十六进制:采用0-9,A-F(10-15),进位规则是“逢十六进一”

/**

 * 进制转换

 */

public class jinzhiTest {

public static void main(String[]args) {

int i = 22;

//二进制  10110 = 1*24+0*23+1*22+1*21+0*20=22 

System.out.println(Integer.toBinaryString(i));  //10110   

//八进制  26 = 2*81+6*80 = 22

System.out.println(Integer.toOctalString(i));  //26

//十六进制16 = 1*161+6*160 = 22

System.out.println(Integer.toHexString(i)); //16

}

}

3、其他相关概念

   字符:是各种文字和符号的总称,包括各个国家的文字、标点符号、图形符号、数字等。

   字符集:字符集是多个符号的集合,每个字符集包含的字符个数不同。

   字符编码:字符集只是规定了有哪些字符,而最终决定采用哪些字符,每一个字符用多少个字节表示等问题,则是由编码来决定的。计算机要准确的处理各个字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。(通过字符编码可以把字符转化成字节)

二、常见的字符集编码介绍

1、ASCII字符集

1.1、定义

   ASCII 字符集:美国信息互换标准代码。是基于罗马字母表的一套电脑编码系统,主要显示英语和一些西欧语言,是现今最通用的单字节编码系统。ASCII字符集只有256个字符,用0-255之间的数字来表示。(使用7位单元的字符集01111111-->256)

1.2、包含内容

   控制字符(回车键、退格、换行键等)

   可显示字符(英文大小写、阿拉伯数字、西文符号)

   扩展字符集(表格符号、计算符号、希腊字符、拉丁符号)

1.3、编码方式

   0-31号及127号是控制字符或通讯专用字符

   32-126号是字符,其中48-57号是0-9是个阿拉伯数字,65-90号为26个大写英文字母,97-122号为26个英文小写字母,其余一些为标点符号,运算符号等。

2、Unicode字符集

2.1、定义

国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java 语言使用的就是unicode)  University multiple-object coded character set(通用多八位编码字符集),支持世界上超过650种语言的国际字符。Unicode允许在同一服务器上混合使用不同语言,它为每种语言的每个字符设定了统一并且唯一的二进制编码,以满足跨平台,跨语言进行文本转换,处理的要求。[ASCII只有256个字符集,这对于许多亚洲和东方语言来说,远远不够,为此Unicode应运而生]

2.2、编码方式

Unicode标准始终使用十六进制数字,固定使用2个字节来表示一个字符,共可以表示65536个字符。而且书写时在前面加上前缀“U+”,例如A的编码是0041,则书写成“U+0041”。  

Unicode编码系统分为编码和实现。编码指的是:每个字符对于的Unicode码是唯一确定的,Unicode用两个字节表示一个字符。这样理论上可以表示65536(2的16次方)个字符。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符。而新版的Unicode定义了16个辅助平面,至少需要占据21位编码空间(比3字节少,但是辅助平面仍然占用4个字节编码空间,与UCS-4保持一致)。UCS-4是一个更大的尚未完全填充的31位字符集。

签名BOM:

*!*   BOM 是 Byte Order Mark 的缩写(字节顺序标记),是编码方案里用于标识编码的标准标记。

*!*   ANSI 文件没有 BOM

*!*   UTF-8 文件的 BOM 为:EF BB BF,不过 UTF-8 文件可以有 BOM,也可以没有 BOM

*!*   Unicode big endian 文件的 BOM 为:FE FF

*!*   Unicode little endian 文件的 BOM 为:FF FE

Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序(BOM)的字符,这个字符的名字叫做"零宽度非换行空格"(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。

如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。

以汉字"严"为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian(最低位地址存放高位字节,可称高位优先,内存从最低地址开始按顺序存放(高数位数字先写)。最高位字节放最前面。)方式;25在前,4E在后,就是Little endian(最低位地址存放低位字节,可称低位优先,内存从最低地址开始按顺序存放(低数位数字先写)。最低位字节放最前面。)方式。

 

Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称UTF)。因为实际操作中,我们因为一些特殊的要求(提高性能、节约空间等),对Unicode会有很多种实现方式。例如:如果一个仅包含基本7位的ASCII码字符,对于Unicode来说每个字符都使用了两个字节的Unicode编码传输,那么其第一个字节的8位始终为0,这就造成了比较大的浪费,对于这种情况,可以使用UTF-8编码。这是一种变长的编码,占它将基本的7位的ASCII字符仍用7位编码表示,占用一个字节(首位补0),而遇到与其他Unicode字符混合的情况,将按照一定的算法转换,每个字符使用1-3个字节编码,并利用首位为0或1进行识别。这样对以7位ASCII字符为主的西文文档就大大的节省了编码的长度。

 

2.3、实现方式

UTF8 :

Unicode 其中的一个使用方式。 UTF-8使用可变长度字节来存储Unicode字符,如ASCII字母还是采用一个字符来存储,希腊字母等采用2个字符来存储,而常用的汉字要使用3字节,辅助平面字符(少量不常用的)则使用4字节。

    UTF-8的编码规则很简单,只有二条:

    a)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。

    b)对于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

-------------------|-------------------------------------------------

以汉字"严"为例:

    已知"严"的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此"严"的UTF-8编码需要三个字节,即格式是"1110xxxx 10xxxxxx 10xxxxxx"。然后,从"严"的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,"严"的UTF-8编码是"11100100 10111000 10100101",转换成十六进制就是E4B8A5。

UTF-16:  

使用一个或两个未分配的16位代码单元的序列对Unicode代码点进行编码,其中大部分汉字采用2个字节编码,少量汉字采用4个字节编码

UTF-32:  

将每一个Unicode代码点表示为相同值的32位整数。 使用4个字节为每个字符编码。占用空间一般是其他编码的2-4倍。

 

3、GB2312 字符集

3.1、定义

信息交换用汉字编码字符集。是中国标准的简体中文字符集,它所收录的汉字已经覆盖99.75%的使用频率,在中国大陆和新加坡广泛使用。1980年制定的。

3.2、包含内容

GB2312收录了简化汉字及一般字符,序号,数字,拉丁字母,日文假名,希腊字母,俄文字母,汉语拼音符号,汉语注音字母,共7445个图形字符。其中包括6763个汉字,一级汉字3755个,二级汉字3008个。

3.3、编码方式:  

GB2312对所收汉字进行了“分区”处理,每区含有94个汉字或者符号,这种表示方法也叫做“区位码”。  它是用双字节表示的,前面的字节为第一字节,又称“高字节”,后面的为第二字节,“低字节”。  高位字节,把01-87区的区号加上0xA0(相当于数字160);低位字节把01-94区的区号加上0xA0(相当于数字160)。 举个简单的小例子:第一个汉字——“啊”,它的区号为16,位号01,则区位码是1601。则高字节位:16+0xA0=0xB0;低字节位:01+0xA0=0xA1,所以“啊”的汉字处理编码为0xB0A1。

Windows 中的代码页(Code Page)是 CP936 。

4、GBK字符集  

l  定义:  

GBK是GB2312字符集的扩展(K)(中国的中文编码表升级,融合了更多的中文文字符号。),它收录了21886个符号,它分为汉字区和图形符号区,汉字区包括21003个字符。GBK字符集主要扩展了繁体中文字的支持。  GBK 作为对 GB2312 的扩展,在现在的 Windows 系统中仍然使用代码页 CP936 表示。

 

扩展:内码和code page(代码页)

    UNICODE 是用两个字节来表示为一个字符,他总共可以组合出65535不同的字符,这大概已经可以覆盖世界上所有文化的符号。如果还不够也没有关系,ISO已经准备了UCS-4方案,说简单了就是四个字节来表示一个字符,这样我们就可以组合出21亿个不同的字符。如果Unicode在全世界人民使用计算机之前发明时就统一了,代码页就没有必要存在。

内码是指操作系统内部的字符编码。早期操作系统的内码是与语言相关的,从Windows NT 开始,MS 趁机把它们的操作系统改了一遍,把所有的核心代码都改成了用 UNICODE 方式工作的版本。现在的Windows 在内部统一使用Unicode(这样在内核上可以支持全世界所有的语言文字),然后用代码页适应各种语言微软一般将缺省代码页指定的编码说成是内码,在特殊的场合也会说自己的内码是Unicode例如在 GB18030问题的处理上。
    Codepage(代码页):就是各国的文字编码和Unicode之间的映射表。例如GBK的code page是CP936 ,BIG5的code page是CP950,GB2312的code page是CP20936。微软也为GB18030定义了code page:CP54936。但是由于GB18030有一部分4字节编码,而Windows的代码页只支持单字节和双字节编码,所以这个code page是无法真正使用的。

    Windows应用程序使用的代码页由windows区域(Locale)设置来定义。系统Locale决定代码页,用户Locale决定数字、货币、时间和日期的格式。

常见的Code Page:

代码页Code Page

对应的字符集Character Set

Code Page 932(cp932)

Japanese

Code Page 936(cp936)

GBK – Simplified Chinese

Code Page 949

Korean

Code Page 950

BIG5 – Traditional Chinese

Code Page 1200

UTF-16LE Unicode little-endian

Code Page 1201

UTF-16BE Unicode big-endian

Code Page 65000

UTF-7 Unicode

Code Page 65001

UTF-8 Unicode

ANSI Code Page

微软自己定义了一系列的Code Page

我们在另存记事本文件时,常常看到默认存的是ANSI编码,ANSI是微软自定义的一系列代码页。(如:GBK、GB312、Big5、Shift_JIS等这些编码方式统称为ANSI编码)在简体中文Window操作系统中,ANSI编码代表GBK,在繁体中文操作系统中,ANSI代表Big5编码,在日文操作系统中,ANSI代表Shift_JIS。

例如:在记事本中输入 “联通” 两个字

保持后,再次打开发现乱码了。记事本默认保持的是ANSI字符编码。在简体中文模式下ANSI默认使用的是GBK编码。GBK编码下联通对应的编码是:

-0xc1aa    二进制排列:1100 00011010 1010

-0xcda8    二进制排列:1100 11011010 1000

 

 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

-------------------|-------------------------------------------------

不巧的是,联通二字符合UTF-8的第二个模板,保持的时候保持的是GBK,当我们打开记事本时,记事本误把该文件当做UTF8编码的文件去解析,即:GBK-转为-UTF8,所以打开就乱码了。

但是用UltraEdit打开,看到的是[jͨ]。

这是因为UltraEdit检测到头部符合UTF-8,到误把文件以UTF-8格式读入,读入后对于联字来说:1100 00011010 1010去掉红色部分:00001 101010

再对齐补0。就得到:0000 0000 0110 1010。转换成编码:0x006a。经过utf-8解析后0x006a对应的Unicode字符是j。同理对于联字来说,转换成编码:0x0368。

public class Test {

public static void main(String[] args)throws UnsupportedEncodingException {

     //GBK编码     Charset只能处理双字节编码

     Charset gb = Charset.forName("GBK");

     ByteBuffer gbbu1 = gb.encode("");

     System.out.println(Integer.toHexString(gbbu1.getChar()));//claa

     ByteBuffer gbbu2 = gb.encode("");

     System.out.println(Integer.toHexString(gbbu2.getChar()));//cda8

    

    Charset cs1 = Charset.forName("GBK");

     ByteBuffer buffer1 = cs1.encode("联通");

    //utf-8编码去解析GBK编码文字

     Charset cs2 = Charset.forName("utf-8");

     CharBuffer jm1 = cs2.decode(buffer1);

     System.out.println("UTF-8解析GBK"+jm1.toString());// ͨ

//类似UltraEdit打开后解析

    System.out.println("\u006a\u0368");//jͨ

}

}

5、BIG5字符集  

5.1、定义  

    又称大五码,由台湾五家软件公司创立。因为当时台湾没有一个标准的字符集,而且GB2312又没有收录繁体字,所以才推出了BIG5。    

5.2、包含内容

    BIG5字符集共收录了13053个中文字,该字符集在台湾使用。但是没有考虑到社会上流通的人名,地方用字,方言用字,化学及生物科等用字,没有包含日文平假名及片假字母。

5.3、编码方式

BIG5也采用双字节存储方法,以两个字节编码一个字。高位字节的编码范围是0xA1-0xF9,低位字节的编码范围是0xA1-0xFE。

 

6、GB18030字符集

6.1、定义

    GB18030字符集标准解决汉字,日文假名,朝鲜语和中国少数民族文字组成的大字符集计算机编码问题。    

6.2、包含内容

    该标准的字符总编码空间超过150万个编码位,收录了27484个汉字,覆盖中文,日文,朝鲜语和中国少数民族文字。满足中国大陆,香港,台湾,日本和韩国等东南亚地区信息交换多文种,大字量,多用途,统一编码格式的要求。    

6.3、编码方式  

GB8030标准采用单字节,双字节和四字节三种方式对字符编码。 单字节部分使用0x00-0x7F码(对应于ASCII码的相应码);  双字节部分,首字节码从0x81-0xFE,尾字节码分别是0x40-0x7E和0x80-0xFE。四字节部分采用0x30-0x39作为双字节编码扩充的后缀,这样扩充的四字节编码,其范围是0x81308130-0x0xFE39FE39,其中第一,三个字节编码位均为0x81-0xFE,第二,四个为0x30-0x39。GB18030 在 Windows 中的代码页是 CP54936。

 

7、ISO-8859-1:拉丁文字母表1号

用一个字节的8位表示。属于单字节编码,应用于英文系列,最多表示的字符范围是0-255,无法表示中文字符。很多协议上,默认使用的是该编码。

 

public class IOTest {

    public static void main(String[]args) throws UnsupportedEncodingException {

     //java中的char字符,采用的是Unicode编码

     System.out.println(Integer.toHexString(''));//9ec4

     System.out.println("\u9ec4");//书写格式 前面加u

    

     //  中  字的Unicode编码为4e2d

     System.out.println(Integer.toHexString(''));//4e2d

     //  中  字的GB2312编码为d6d0  getChar()  只能处理两个字节的编码规则

     Charset gb = Charset.forName("GB2312");

     ByteBuffer gbbu = gb.encode("");

     System.out.println(Integer.toHexString(gbbu.getChar()));//d6d0

 

      String str = "";

      byte[]utf8Bytes = str.getBytes("utf-8");

         System.out.println("uft8:" +bytesToHexString(utf8Bytes));//e4b8ad

         

         //注:前面的feff就表示该文件采用大头方式存储

         String str1 = "";

      byte[]utf8Bytes1 = str1.getBytes("utf-16");

         System.out.println("utf-16:" +bytesToHexString(utf8Bytes1));//feff e42d

         

         String str2 = "";

      byte[]utf8Bytes2 = str2.getBytes("Unicode");

         System.out.println("Unicode:" +bytesToHexString(utf8Bytes2));//feff e42d

}

    public static String bytesToHexString(byte[]src){         

        StringBuilder stringBuilder = new StringBuilder();         

        if (src ==null || src.length <= 0) {         

            return null;         

        }         

        for (int i = 0;i < src.length; i++) {         

            int v =src[i] & 0xFF;    //之所以用byte0xff相与,是因为int32位,与0xff相与后就舍弃前面的24位,只保留后8位    

            String hv = Integer.toHexString(v);         

            if (hv.length() < 2) {         //不足两位要补0

                stringBuilder.append(0);

            }        

            stringBuilder.append(hv);  

        }         

        return stringBuilder.toString();         

    }

}

 

为什么会出现乱码?

public class IoTest2 {

public static void main(String[]args) throws UnsupportedEncodingException {

     //GBK解析GBK

     Charset cs1 = Charset.forName("GBK");

     ByteBuffer buffer1 = cs1.encode("");

     System.out.println("GBK解析GBK"+cs1.decode(buffer1));//GBK:号

     //GB2312解析 GBK

     ByteBuffer buffer2 = cs1.encode("");

     Charset cs2 = Charset.forName("GB2312");

     CharBuffer jm2 = cs2.decode(buffer2);

     System.out.println("GB2312解析 GBK"+jm2.toString());//GB2312:号

     //BIG5解析 GBK

     ByteBuffer buffer3 = cs1.encode("");//GBK

     Charset cs3 = Charset.forName("BIG5");

     CharBuffer jm3 = cs3.decode(buffer3);

     //虽然找到了,但是字符编码并不同

        System.out.println("BIG5解析 GBK"+jm3.toString());//BIG5:瘍

     /**

     * GBK解析UTF-8e58fGBK中的编码对应的是鍙字GBK是双字节编码,

     *由于位数差异,多出来一个字节在GBK中无法找到对应的编码

     */

     Charset cs4 = Charset.forName("UTF-8");

     ByteBuffer buffer4 = cs4.encode("");

     System.out.println(bytesToHexString(buffer4.array()));//e58fb7

     CharBuffer jm4 = cs1.decode(buffer4);

        System.out.println("GBK解析 UTF-8:"+jm4.toString());//

        

        Charset cs5 = Charset.forName("GBK");

     ByteBuffer buffer5 = cs5.encode("");

     System.out.println(bytesToHexString(buffer5.array()));//bac5

     CharBuffer jm5 = cs4.decode(buffer5);

     //找不到对应的编码只有两位所以出现两个,中文在UTF-8中占三个字符

        System.out.println("UTF-8解析 GBK  :"+jm5.toString());//��

}

public static String bytesToHexString(byte[]src){         

        StringBuilder stringBuilder = new StringBuilder();         

        if (src ==null || src.length <= 0) {         

            return null;         

        }         

        for (int i = 0;i < src.length; i++) {         

            int v =src[i] & 0xFF;    //之所以用byte0xff相与,是因为int32位,与0xff相与后就舍弃前面的24位,只保留后8位    

            String hv = Integer.toHexString(v);         

            if (hv.length() < 2) {         //不足两位要补0

                stringBuilder.append(0);

            }        

            stringBuilder.append(hv);  

        }         

        return stringBuilder.toString();         

    }

}

 

字节长度:

public class CodeLength{

public static void main(String[] args)throws UnsupportedEncodingException {

    System.out.println("".getBytes("GBK").length);//2

    System.out.println("".getBytes("UTF-8").length);//3

    System.out.println("".getBytes("unicode").length);//4

    System.out.println("".getBytes("ISO8859-1").length);//1

    System.out.println("".getBytes().length);  //3

        

        Charset cs1 = Charset.forName("Unicode");

         ByteBuffer buffer1 = cs1.encode("");

         System.out.println(bytesToHexString(buffer1.array()));    

                                                                    //feff6df100

}

    public static String bytesToHexString(byte[] src){         

        StringBuilder stringBuilder = new StringBuilder();         

        if (src ==null || src.length <= 0) {         

            return null;         

        }         

        for (int i = 0; i < src.length; i++) {         

            int v = src[i] & 0xFF;    //之所以用byte0xff相与,是因为int32位,与0xff相与后就舍弃前面的24位,只保留后8位    

            String hv = Integer.toHexString(v);         

            if (hv.length() < 2) {         //不足两位要补0

                stringBuilder.append(0);

            }        

            stringBuilder.append(hv);  

        }         

        return stringBuilder.toString();         

    }

}

GBK编码:一个中文占2个字节

UTF-8编码:一个中文占3个字节

Unicode编码:一个中文占4个字节,Unicode本身是采用两个字节编码的,在java中直接使用Unicode转码时会按照UTF-16LE的方式拆分,由于UTF-16分为UTF-16LE/UTF-16BE,直接使用无法区分是大端还是小端,所以会加上一个额外的字节序BOM头。开头两个字节FE FF-(BE),开头两个字节FF FE-(LE)刚好占据了两个字节。所以会显示占据4个字节长度

ISO8859-1编码:单字节编码,对于中文无法解析,就算解析也只能取其一个字节编码

"".getBytes()也返回的是3,这是因为文件格式默认设置的UTF-8编码。按照默认编码获取的字节长度为3

 

 

1 0
原创粉丝点击