编码与解码

文章目录

  • 编码与解码
    • 前言
    • 一、什么是编码与解码
    • 二、常见的编码表
      • 1、ASCII
      • 2、ISO-8859-1
      • 3、windows-1252
      • 4、GB2312
      • 5、GBK
      • 6、GB18030
      • 7、Big5
      • 8、编码表汇总
      • 9、乱码和兼容
        • 9.1、兼容
        • 9.2、乱码
      • 10、Unicode
        • 10.1、UTF-32
        • 10.2、UTF-16
        • 10.3、UTF-8
        • 10.4、BOM
      • 11、乱码的原因和可逆性
        • 11.1、乱码原因
        • 11.2、乱码可逆情况
        • 11.3、乱码不可逆情况
    • 三、Java的char字符
    • 四、String类
      • 1、编码的方法
        • 1.1、getBytes()方法
        • 1.2、getBytes(String charsetName)方法
      • 2、解码的方法
        • 2.1、String(byte[] code)
        • 2.2、String(byte[] code,String charsetName)
      • 3、乱码的情况
        • 3.1、可逆的情况
        • 3.2、不可逆的情况
        • 3.3、ISO-8895-1编码的妙用
    • 五、IO流-字符流
      • 1、InputStreamReader
        • 1.1、正常
        • 1.2、乱码
      • 2、OutputStreamWriter
        • 2.1、正常
        • 2.2、乱码
      • 3、复制文件
        • 3.1、字符流复制文本乱码因素
        • 3.2、字符流UTF-8 编码复制图片
        • 3.3、ISO-8859-1的妙用

前言

最近在做一个是有关网络通信的项目,接收数据后就老是遇到乱码的现象,多次上百度搜索后觉得不如从底层了解编码与解码,于是就在网上找了一个有关编码与解码的视频,之后再加上自己的一些小理解整理了这篇笔记,总的来说,我感觉这一篇文章能够很好帮助理解编码与解码,以及乱码产生的底层原因。

一、什么是编码与解码

电脑是由电路板组成,电路板里面集成了无数的电阻和电容, 交流电经过电容的时候,电压比较低记为低电平 ,用0表示,交流电流过电阻的时候,电压比较高,记为高电平,用1来表示; 所以每一个1和0在计算机中被称为,也就是bit位

然而,如果使用一个位来表示计算机中的最小存储单元, 那么这个存储单元只能存储0或者1,存储的范围太小了,所以我们规定用用8个bit位为一组来表示计算机的最小存储单元,这个最小存储单元就是byte(字节)。

8个位每个位上能存储0或者1,则byte的存储范围则是00000000-11111111(换算成整数即0-255)。

计算机的底层只能存储0和1,如果是日常生活中遇到的数字 比如 127 ,这个可以通过10进制和二进制的转换从而让计算机存储01111111,但是如果计算机存储类似于汉字、英文字符、符号字符等内容,是如何存储的呢?

根据上图解释说明,计算机提供了很多的编码表记录了字符和数字的一一对应关系,编码就是把字符对应编码表中的码值存储在电脑中,而解码则是把码值在编码表中的对应的字符展现出来。

注意:计算机中存储一个数是用二进制来表示的。
比如存储127,那么计算机的底层是01111111。
人看这些二进制的数通常都是眼花缭乱的,如何方便而规整的表示这些二进制数呢,不妨引入十六进制。二进制换算成十六进制,则是每四位为一组转换为16进制数即可。
比如01111111这个数前4位0111转换为7,后4位转换为F,则最终的16进制数是7F。
一般繁琐的二进制数使用十六进制数来表示会比较方便规整,所以人们习惯用十六进制数来表示码值。

计算机提供了哪些编码表呢?

二、常见的编码表

1、ASCII

世界上虽然有各种各样的字符,但计算机发明之初没有考虑那么多,基本上只考虑了美国的需求,美国大概只需要128个字符,美国就规定了这128个字符的二进制表示方法,这个方法是一个标准,称为ASCII编码(American Standard Code for Information Interchange,美国信息互换标准代码)。

128个字符用7个位刚好可以表示,计算机存储的最小单位是byte,即8位,ASCII码中最高位设置为0,用剩下的7位表示字符。这7位可以看做数字0到127,ASCII码规定了从0到127个,每个数字代表什么含义。我们先来看数字32到126的含义,如下图所示,除了中文之外,我们平常用的字符基本都涵盖了,键盘上的字符大部分也都涵盖了。

数字32到126表示的这些字符都是可打印字符,0到31和127表示一些不可以打印的字符,这些字符一般用于控制目的,这些字符中大部分都是不常用的,下表列出了其中相对常用的字符。

ASCII码对美国是够用了,但对别的国家而言却是不够的,于是,各个国家的各种计算机厂商就发明了各种各样的编码方式以表示自己国家的字符。

为了保持与ASCII码的兼容性,一般都是将最高位设置为1。也就是说,当最高位为0时,表示ASCII码,当为1时就是各个国家自己的字符

在这些扩展的编码中,在西欧国家中流行的是ISO 8859-1和Windows-1252,在中国是GB2312、GBK、GB18030和Big5,我们逐个来研究这些编码。


2、ISO-8859-1

ISO 8859-1又称Latin-1,它也是使用一个字节表示一个字符,因为西欧的文字也都是字母拼接,只不过不是26个英文字母罢了,其中0到127与ASCII一样,128到255规定了不同的含义(换句话说,就是兼容ASCII编码)

在128到255中,128到159表示一些控制字符,这些字符也不常用,就不介绍了。160到255表示一些西欧字符,如下图所示:


3、windows-1252

ISO 8859-1虽然号称是标准,用于西欧国家,但它连欧元() 这个符号都没有,因为欧元比较晚,而标准比较早。

实际使用中更为广泛的是Windows-1252编码,这个编码与ISO8859-1基本是一样的,区别只在于数字128到159,Windows-1252使用其中的一些数字表示可打印字符,这些数字表示的含义,如下图所示:

这个编码中加入了欧元符号以及一些其他常用的字符。基本上可以认为,ISO 8859-1已被Windows-1252取代,在很多应用程序中,即使文件声明它采用的是ISO 8859-1编码,解析的时候依然被当做Windows-1252编码。

HTML5 甚至明确规定,如果文件声明的是ISO 8859-1编码,它应该被看做Windows-1252编码。为什么要这样呢?因为大部分人搞不清楚ISO 8859-1和Windows-1252的区别,当他说ISO 8859-1的时候,其实他实际指的是Windows-1252,所以标准干脆就这么强制了。


4、GB2312

美国和西欧字符用一个字节就够了,但中文显然是不够的。中文第一个标准是GB2312。

GB2312标准主要针对的是简体中文常见字符,包括约7000个汉字,不包括一些罕见词,不包括繁体字。GB2312固定使用两个字节表示汉字,在这两个字节中,最高位都是1若最高位是0就认为是ASCII字符

在这两个字节中,其中第一个字节范围是1010 0001(十进制161) - 1111 0111(十进制247),第二个字节范围是1010 0001(十进制161) - 1111 1110(十进制254)

比如,“贤哥”的GB2312编码如下:

CF, CD B8 , E7

为了方便的查看二进制—十进制—十六进制的转换 ,可以使用下面的两个方法。

/**
格式化打印:0b1111 -> 二进制: 1111  十进制: 15  十六进制: F*/
private static void printFormatFromBinary(int binary) {System.out.println("二进制: "+Integer.toBinaryString(binary)+"  十进制: "+binary+"  十六进制: "+Integer.toHexString(binary).toUpperCase());
}/**
格式化打印:0xFF -> 二进制: 11111111  十进制: 255  十六进制: FF
*/
private static void printFormatFromHex(int hex) {System.out.println("二进制: "+Integer.toBinaryString(hex)+"  十进制: "+hex+"  十六进制: "+Integer.toHexString(hex).toUpperCase());
}

5、GBK

GBK建立在GB2312的基础上,向下兼容GB2312,也就是说,GB2312编码的字符的二进制表示,在GBK编码里是完全一样的。

GBK增加了一万四千多个汉字,共计约21000汉字,其中包括繁体字。GBK同样使用固定的两个字节表示,其中第一个字节范围是1000 0001(十进制129) - 1111 1110(十进制254),第二个字节范围是0100 0000(十进制64) - 0111 1110(十进制126)1000 0000(十进制128) - 1111 1110(十进制254)

需要注意的是,第二个字节是从64开始的(64属于byte正数范围,和ASCII的编码重合了),也就是说,第二个字节最高位可能为0。那怎么知道它是汉字的一部分,还是一个ASCII字符呢?

其实很简单,因为汉字是用固定两个字节表示的,在解析二进制流的时候,如果第一个字节的最高位为1,那么就将下一个字节读进来一起解析为一个汉字,而不用考虑它的最高位,解析完后,跳到第三个字节继续解析。


6、GB18030

GB18030向下兼容GBK,增加了五万五千多个字符,共七万六千多个字符。包括了很多少数民族字符,以及中日韩统一字符。

用两个字节已经表示不了GB18030中的所有字符,GB18030使用变长编码,有的字符是两个字节,有的是四个字节

  • 在两字节编码中,字节表示范围与GBK一样。
  • 在四字节编码中,第一个字节的值从1000 0001(十进制129)11111110(十进制254),第二个字节的值从0011 0000(十进制48)0011 1001(十进制57),第三个字节的值从1000 0001(十进制129)11111110(十进制254),第四个字节的值从0011 0000(十进制48)0011 1001(十进制57)

解析二进制时,如何知道是两个字节还是四个字节表示一个字符呢?

很简单,看第二个字节的范围,如果是48到57就是四个字节表示,因为两个字节编码中第二字节都比这个大。所以这样综合说明GB18030兼容GBK,兼容GB2312,兼容ASCII,但是GB18030,GBK,GB2312这三个编码和ISO8859-1是不兼容的哦。


7、Big5

Big5是针对繁体中文的,广泛用于台湾香港等地。

Big5包括1万3千多个繁体字,和GB2312类似,一个汉字同样固定使用两个字节表示。在这两个字节中,第一个字节范围是10000001(十进制129)1111 1110(十进制254),第二个字节范围是0100 0000(十进制64) - 0111 1110(十进制126)1010 0001(十进制161) - 1111 1110(十进制254)

Big5和GB18030,GBK,GB2312不兼容哈,如果已经理解了上文,其实你就能理解为什么Big5和GB的三个编码为什么不兼容了。


8、编码表汇总

我们简单汇总一下上面的内容。ASCII码是基础,一个字节表示,最高位设为0,其他7位表示128个字符

其他编码都是兼容ASCII的,最高位使用1来进行区分

  • 西欧主要使用Windows-1252,使用一个字节,增加了额外128个字符。
  • 中文大陆地区的三个主要编码GB2312,GBK,GB18030,有时间先后关系,表示的字符数越来越多,且后面的兼容前面的,GB2312和GBK都是用两个字节表示,而GB18030则使用两个或四个字节表示。
  • 香港台湾地区的主要编码是Big5。

如果文本里的字符都是ASCII码字符,那么采用以上所说的任一编码方式都是一样的,不会乱码。

但如果有高位为1的字符,除了GB2312/GBK/GB18030外,其他编码都是不兼容的,比如,Windows-1252和中文的各种编码是不兼容的,即使Big5和GB18030都能表示繁体字,其表示方式也是不一样的,而这就会出现所谓的乱码。


9、乱码和兼容

9.1、兼容

  • GB2312/GBK/GB18030与ASCII是兼容的,比如我们文本里面"a"字符,使用这四种码表任何一种都是可以正常显示的;
  • windows-1252、ISO-8859-1和ASCII是兼容的;
  • Big5和ASCII是兼容的。

但是西欧编码、Big5以及GB系列的编码,它们相互之间是不兼容的,也就是同样的码值在三种编码表中显示的内容是不一样的。

9.2、乱码

如果编码的时候同一种编码表,而解码的时候通过的却是一种不兼容的编码表(编码和解码使用的是不兼容的编码表),则就就会出现乱码现象。


10、Unicode

以上我们介绍了中文和西欧的字符与编码,但世界上还有很多的国家的字符,每个国家的各种计算机厂商都对自己常用的字符进行编码,在编码的时候基本忽略了别的国家的字符和编码,甚至忽略了同一国家的其他计算机厂商,这样造成的结果就是,出现了太多的编码,且互相不兼容。

世界上所有的字符能不能统一编码呢?可以,这就是Unicode

Unicode做了一件事,就是给世界上所有字符都分配了一个唯一的数字编号,这个编号范围从0x0000000x10FFFF,包括110多万。

但大部分常用字符都在0x0000 - 0xFFFF之间,即65536个数字之内。每个字符都有一个Unicode编号,这个编号一般写成16进制,在前面加U+。大部分中文的编号范围在U+4E00U+9FA5,例如,"贤"的Unicode是U+8D24

Unicode只是给所有字符分配了唯一数字编号,并没有规定这个编号怎么对应到二进制表示

这是与上面介绍的其他编码不同的,其他编码都既规定了能表示哪些字符,又规定了每个字符对应的二进制是什么,而Unicode本身只规定了每个字符的数字编号是多少。

1990年开始研发,1994年正式公布。随着计算机工作能力的增强,Unicode也在面世以来的十多年里得到普及。
Unicode6.3版已发布(2013年11月)。在Unicode联盟网站上可以查看完整的6.3的核心规范。Unicode定义了大到足以代表人类所有可读字符的字符集。
Unicode其实应该是一个码值表。Unicode的作用是为每一个字符提供一个唯一的数字码,而对数字码的存储规则的定义则需要依靠UTF-8/UTF-16/UTF-32。
UTF-8/UTF-16/UTF-32是通过对Unicode码值进行对应规则转换后,编码保持到内存/文件中。
UTF-8/UTF-16都是可变长度的编码方式。

那编号怎么对应到二进制表示呢?有多种方案,主要有UTF-32UTF-16UTF-8

10.1、UTF-32

这个最简单,就是字符编号的整数二进制形式(四个字节、32位)

但有个细节,就是字节的排列顺序,如果第一个字节是整数二进制中的最高位,最后一个字节是整数二进制中的最低位,那这种字节序就叫“大端”(Big Endian, BE),否则,正好相反的情况,就叫“小端”(Little Endian, LE)。对应的编码方式分别是UTF-32BE和UTF-32LE。比如

Unicode编码 UTF32-LE UTF32-BE
0x006C49 49 6C 00 00 00 00 6C 49
0x020C30 30 0C 02 00 00 02 0C 30
注意:之所以有大端和小端两种方式,是因为硬件读写顺序的不同。
大端:数据的高字节保存在内存的低地址中,低字节保存到内存的高地址中,和我们的阅读习惯一致;小端则相反,常用的X86结构是小端模式。
采用大端方式进行数据存放符合人类的正常思维,而采用小端方式进行数据存放利于计算机处理。

可以看出,每个字符都用四个字节表示,非常浪费空间,实际采用的也比较少。

注意:UTF-32是因为UTF-16编码方式不能表示全部的字符而扩充的编码方式

10.2、UTF-16

在了解 UTF-16 编码方式之前,先了解一下另外一个概念——“平面”。

在上面的介绍中,提到了 Unicode 可以看成是一本很厚的字典,它将全世界所有的字符定义在一个集合里。这么多的字符不是一次性定义的,而是分区定义。每个区可以存放 65536 个(2^16)字符,称为一个平面(plane)。目前,一共有 17 个(2^5)平面(65536*17 = 1,114,112‬ 也就是110多万),也就是说,整个 Unicode 字符集的大小现在是 2^21

最前面的 65536 个字符位,称为基本平面(简称 BMP ),它的码点范围是从 0 到 2^16-1,写成 16 进制就是从 U+0000U+FFFF。所有最常见的字符都放在这个平面,这是 Unicode 最先定义和公布的一个平面。剩下的字符都放在辅助平面(简称 SMP ),码点范围从 U+010000U+10FFFF

基本了解了平面的概念后,再说回到 UTF-16。UTF-16 编码介于 UTF-32 与 UTF-8 之间,同时结合了定长变长两种编码方法的特点。

UTF-16 的编码规则很简单:基本平面的字符占用 2 个字节,辅助平面的字符占用 4 个字节。也就是说,UTF-16 的编码长度要么是 2 个字节(U+0000U+FFFF),要么是 4 个字节(U+010000U+10FFFF)。那么问题来了,当我们遇到两个字节时,到底是把这两个字节当作一个字符还是与后面的两个字节一起当作一个字符呢?

为了将两个字节的UTF-16编码与四个字节的UTF-16编码区分开来,Unicode编码的设计者将0xD800 - 0xDFFF保留下来,并称为代理区(Surrogate):

辅助平面的字符位共有 2^20 个,因此表示这些字符至少需要 20 个二进制位。UTF-16 将这 20 个二进制位分成两半,前 10 位映射在 U+D800U+DBFF,称为高代理位(H),后 10 位映射在 U+DC00U+DFFF,称为低代理位(L)。这意味着,一个辅助平面的字符,被拆成两个基本平面的字符表示。

D800-DB7F High Surrogates 高位替代
DC00-DFFF Low Surrogates 低位替代

如果U≥0x10000,我们先计算U’=U-0x10000,然后将U’写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yy yyyyyyyy 110111xx xxxxxxxx

按照上述规则,Unicode编码0x10000 - 0x10FFFF的UTF-16编码有四个字节,前两个字节的高6位是110110,后两个字节的高6位是110111。可见,前两个字节的取值范围(二进制)是11011000 0000000011011011 11111111,即0xD800 - 0xDBFF。后两个字节取值范围(二进制)是11011100 0000000011011111 11111111,即0xDC00 - 0xDFFF

因此,当我们遇到两个字节,发现它的码点在 U+D800U+DBFF 之间,就可以断定,紧跟在后面的两个字节的码点,应该在 U+DC00U+DFFF 之间,这四个字节必须放在一起解读。

接下来,以汉字"

详解Java编码与解码以及常见的编码表,灵活处理乱码问题相关推荐

  1. 详解Base64编码和解码

    Base64是最常用的编码之一,比如开发中用于传递参数.现代浏览器中的<img />标签直接通过Base64字符串来渲染图片以及用于邮件中等等.Base64编码在RFC2045中定义,它被 ...

  2. 视频教程-javascript/jquery全过程详解-Java

    javascript/jquery全过程详解 资深大数据.java讲师,十年开发经验,曾经任职于北大青鸟.讯腾软件等多家知名教育机构,精通javaweb, 前端技术,J2EE技术体系,熟练使用Spri ...

  3. 详解java中的final关键字

    概述 final 简介 final关键字可用于多个场景,且在不同场景具有不同的作用.首先,final是一个非访问修饰符,仅适用于变量,方法或类.下面是使用final的不同场景: 上面这张图可以概括成: ...

  4. 详解Java解析XML的四种方法

    http://developer.51cto.com  2009-03-31 13:12  cnlw1985  javaeye  我要评论(8) XML现在已经成为一种通用的数据交换格式,平台的无关性 ...

  5. 详解Java中的正则表达式

    详解Java中的正则表达式,并列出常用的正则表达式语法和一些常用的场景. 判断一个字符串是否是由数字组成: 当不使用正则表达式的时候的实现代码: public class RegexDemo01 {p ...

  6. java 配置文件的路径_详解java配置文件的路径问题

    详解java配置文件的路径问题 详解java配置文件的路径问题 各种语言都有自己所支持的配置文件,配置文件中有很多变量是经常改变的.不将程序中的各种变量写死,这样能更方便地脱离程序本身去修改相关变量设 ...

  7. java的注解方式_详解Java注解的实现与使用方法

    详解Java注解的实现与使用方法 Java注解是java5版本发布的,其作用就是节省配置文件,增强代码可读性.在如今各种框架及开发中非常常见,特此说明一下. 如何创建一个注解 每一个自定义的注解都由四 ...

  8. java web ip_详解Java Web如何限制访问的IP的两种方法

    前一阵子因为在做项目时碰到了这个功能,现在好好总结一下,至于为什么要限制IP访问,我就不多说了.然后百度了一下,现在主要有两种方式去限制IP访问,第一种是最简单的方便的,第二种是通过过滤器来限制访问. ...

  9. java中的静态变量的作用域_详解JAVA中static的作用

    1.深度总结 引用一位网友的话,说的非常好,如果别人问你static的作用:如果你说静态修饰 类的属性 和 类的方法 别人认为你是合格的:如果是说 可以构成 静态代码块,那别人认为你还可以: 如果你说 ...

  10. java io字符输出流_灵魂一击!详解Java中的IO输入输出流

    什么是流?流表示任何有能力产生数据的数据源对象或者是有能力接收数据的接收端对象,它屏蔽了实际的I/O设备中处理数据的细节. IO流是实现输入输出的基础,它可以很方便地实现数据的输入输出操作,即读写操作 ...

最新文章

  1. PHP面向对象编程(imooc)代码合集(四)
  2. sys.stdout.write与sys.sterr.write(二)
  3. alias cli3 配置_vue-cli3全面配置详解
  4. 一个创业者的妥协与希望
  5. C++ 获取类型信息
  6. 【jeecg-mybatis版本】 mybatis+spring mvc 完美整合方案 查询,保存,更新,删除自动生成
  7. node.js安装配置教程
  8. Fuji-ImageJ分割中央凹无血管区
  9. lc用U盘更新固件_索尼电视安卓8.0固件升级完后电视连不上WIFI?最新解决方法!...
  10. STM32——滴答定时器设置1us问题
  11. 计算机网络定义记不住,win10下微软拼音输入法记不住自定义输入词语怎么办
  12. python虚拟变量回归_Python中使用虚拟变量的OLS最佳解决方案?
  13. Revel框架基本使用和搭建教程
  14. 命令行窗口-隐藏黑窗口
  15. Python对excel文件批量加密(GUI选择)
  16. 2022年宋干节活动-乌隆他尼皇家大学
  17. 巧用友盟UShare、ULink玩转裂变营销
  18. 详解2.5G/5G/10G Base-T以太网接口物理层一致性测试!
  19. 保密相册计算机,‎加密相册(新版) - 密码计算机照片保险箱 im App Store
  20. 【20210205期AI简报】联发科发布二代5G基带芯片发布、超强镜像优化从1.16GB到22.4MB!...

热门文章

  1. 如何用Android Stuido 调用百度翻译的API
  2. 互联网十大网络流行语
  3. TP6.0 一对一模型关联 hasOne
  4. sklearn:OneHotEncoder的简单用法
  5. oracle adpatch 回退,Oracle EBS施用adpatch工具打patch过程
  6. 教你怎么用手机进入路由器管理界面
  7. C#笔记——自动关机or定时关机小程序
  8. 关于iOS7越狱的整理
  9. 一、贴片电阻大小的识别与常用的原理图标注规范
  10. 樊登读书搞定读后感_读书笔记 1 :《读懂一本书-樊登读书法》