java中的字符编码简介

    xiaoxiao2021-03-25  71

    计算机常见编码

    一、有关编码的基础知识

    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*2^4+0*2^3+1*2^2+1*2^1+0*2^0=22  System.out.println(Integer.toBinaryString(i));  //10110    //八进制  26 = 2*8^1+6*8^0 = 22 System.out.println(Integer.toOctalString(i));  //26 //十六进制 16 = 1*16^1+6*16^0 = 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 | 0xxxxxxx0000 0080-0000 07FF | 110xxxxx 10xxxxxx0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx0001 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 0001 1010 1010

    通-0xcda8    二进制排列:1100 1101 1010 1000

     

     Unicode符号范围   |  UTF-8编码方式

    (十六进制)          |      (二进制)--------------------|------------------------------------------------

    0000 0000-0000 007F | 0xxxxxxx0000 0080-0000 07FF | 110xxxxx 10xxxxxx0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

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

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

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

    这是因为UltraEdit检测到头部符合UTF-8,到误把文件以UTF-8格式读入,读入后对于联字来说:1100 0001 1010 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;    // 之所以用byte和0xff相与,是因为int是32位,与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-8 :e58f在GBK中的编码对应的是鍙字,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;    // 之所以用byte和0xff相与,是因为int是32位,与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;    // 之所以用byte和0xff相与,是因为int是32位,与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

     

     

    转载请注明原文地址: https://ju.6miu.com/read-34073.html

    最新回复(0)