java编码和解码的规则

java编码和解码的规则

我们先看看编码和解码的一些规则。

解码:把二进制数据转换为真实字符串的数据

编码:把真实的字符串数据转换为二进制数据

常见的编码表:

ASCII:美国标准信息交换表

ISO8859-1:拉丁码表,欧洲码表

GB2312:中国的中⽂编码表

GBK:中国的中⽂编码表升级

GB18030:GBK的取代版本

BIG5:通⽤于⾹港、台湾地区的繁体字编码⽅案

UTF-8:最多⽤3个⼦节表⽰⼀个字符

Unicode:国际标准码,融合了多种⽂字,所有的⽂字都⽤两个字节来表⽰,Java语⾔使⽤的就是该码表

在Unicode字符集下面有三个编码格式:分别是UTF-8、UTF-16、UTF-32。

在这里插入图片描述

字符集就是一系列的字符集合,且里面每一个字符的对应了一个唯一的整数,这个整数叫作码点。UTF-16是一种可变长的编码方式,它可以将某个Unicode字符转换为2个字节或4个字节的二进制数。它什么时候转换为2个字节呢?首先说Unicode就像是一张表,他可以让字符对应一个数字,然后数字对应什么二进制就看他用的是UTF-8、UTF-16还是UTF-32编码了。UTF-16会把码点<65536的码点数字之间转换为对应的二进制数字。比如下面这个“安”这个字符的Unicode码点是23433,即对应的二进制位01011011 10001001,把他用UTF-16编码转为二进制数的结果是下面这样

在这里插入图片描述

当码点=>65536时采用下面的步骤来编码:(虽然H和L是10位的,但是加完后的W1和W2是16位的,所以结果是4字节的)

在这里插入图片描述

上面的步骤比较抽象,下面看看具体是怎么操作的(这个笑脸字符对应的Unicode码点是128516):

在这里插入图片描述

对于码点值小于65536如果他的码点值刚好在[0xD800,0xDFFF]时,不是会和高位代理项和低位代理项冲突吗?Unicode字符集的设计者在设计的时候考虑到这个问题,所以,他们在码点为[0xD800,0xDFFF]区间范围没有设置字符。码点在[0xD800,0xDFFF]范围内是UTF-16的代理项专用的,[0xD800,0xDBFF]是高位代理,[0xDC00,0xDFFF]是放低位代理。还有就是:UTF-16、UTF-8都是自同步的编码,就是指任意一个代码单元都可以不用看前面的代码单元,判断这个这个代码单元是某一个代码单元的开始单元。什么叫代码单元呢?代码单元就是字符编码过程中的基本单位,在UTF-8中一个代码单元是8字节,在UTF-16中是2字节

在这里插入图片描述

在java8的内部,String内部使用的是UTF-16的编码,并用char数组来存数据。String内部的一个char不是对应一个Unicode字符,而是对应一个UTF-16中的一个代码单元。因为对于大部分的常用的字符,包括汉字,他们的码点都是小于65536的,所以他们都可以用一个char表示,但是但是对于码点大于等于65536的字符,比如那个笑脸,他就需要两个连续的char来表示了,一个char对于高位代理项,一个对于低位代理项。所以下面这样的一个String字符“A安😊”对应的二进制数是:

在这里插入图片描述

所以String的length()实际上不是返回字符的长度,而是返回的这串字符串对应的代码单元的长度。charAt(int index)返回的是index对于的代码单元,而不是String中第index+1个字符。例子如下:

在这里插入图片描述

所以如果要求你计算String字符串的长度,且这个字符串里面包含Emoji表情,且一个表情算一个字符,你就应该用String对象的codePointCount方法来计算长度了,他是真的把一个字符串的一个字符当作一个字符。要是要取出一个字符,就算是遇到一个Emoji表情也当一个字符取出的话,就应该用codePointAt()方法来取代charAt()方法。

但是因为UTF-16对于码点小于256的字符表示时,会浪费一个字节,你看下面的0000000都是浪费的

在这里插入图片描述

所以java9对他进行了优化,String底层采用byte数组代替了char数组,并且java9之后,他们的编码格式也变了

在这里插入图片描述

java9对String的优化其实就是,先把底层用byte数组代替char数组,然后用了一个codec标识,当你创建对象时,要是字符串中的字符都是Latin-1编码的字符,即那些字符的码点都是0-255之间的字符(Latin-1的范围就是0-255,asc码的范围是0-127,所以在Latin-1中0x00-0x7F之间完全和ASCII一致(0x7F即127),0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号),那么就用latin-1编码。要是字符串里面有一个不是Latin-1字符,那么就用UTF-16编码,即每个字符都用2个或4个字节来存储,上面讲过的那个规则。

在这里插入图片描述

然后看一个例子:看到这个“abc安”是8个字节的的数组,所以有一个不是Latin-1的字符就采用UTF-16的编码。

在这里插入图片描述

为什么有一个不是Latin-1的字符就用全部都是UTF-16编码呢?因为这样的话,对于程序员来说,虽然里面的结构变了,但是,为了保持使用者的习惯,你java9使用的时候length()方法的效果和java8使用length的效果就一样了,不让程序员就得集体颠覆以前的length()方法的知识点。

但是,为什么你用java9来写一个这样的程序,本来应该是8个字节的长度,但是结果却显示5。显示5表示字节长度是10。因为UTF-16两个字节一个代码单元,然后length()方法其实是返回代码单元的个数,所以底层应该是存了一个10的长度的字节数组。

在这里插入图片描述

为什么呢?

因为他的设置:原来是这样的

在这里插入图片描述

把他改为这样的,Global Encoding是让编译器以指定的格式去解析你的源码对应的二进制数据。

在这里插入图片描述

然后执行就是返回的结果就是8字节的了。

在这里插入图片描述

那么为什么呢?

因为java源码到运行的过程是这样的:java源码会以指定格式编码为java字节码文件,如果不指定就会用操作系统的默认编码格式。编译之后,源码的字符串字面量都是会以Modified UTF-8(魔改的UTF-8,专用于JVM字节码的一种编码)的编码格式转为二进制存到.class文件中。不管你源代码文件用什么字符编码为字节码文件都是以Modified UTF-8存储的那个二进制文件的。

在这里插入图片描述

然后JVM加载类文件时,会把Modified UTF-8编程成的数据以UTF-16的格式存储在内存中的。

在这里插入图片描述

java执行过程整个编码过程为:

在这里插入图片描述

  • 源代码编码是开发人员指定的,建议都使用UTF-8。相当于你写的代码(你看见的代码)会用你指定的格式UTF-8的格式转换为对应的二进制文件。
  • 编译器编码是编译器用哪个编码规则来解析源文件生成的二进制文件,解析后生成一个新的二进制文件给上图中的3字节码编码。可以通过用命令行执行可以用javac 的-encoding命令来指定,不指定用默认的来解析(默认就是用GBK编码格式)。如果在IDEA中,可以设置Global Encoding来设置,这个Global Encoding在哪个位置,前面的截图里有。
  • 字节码编码和JVM内存的编码都是固定的,你变不了,字节码编码用的是Modified UTF-8,JVM内存编码用的是UTF-16。字节码编码会用Modified UTF-8识别2生成的二进制文件,然后产生一个.class文件。然后JVM用UTF-16去识别这个.class文件。
  • 输出流的编码就是你流里面设置为:以什么编码格式来编码你的数据,然后输出到某个文件里面去。输出流的编码就是把jvm处理后的数据,即那个UTF-16数据,以指定的格式去输出到文件中去,生成的东西也是一个二进制数据。
  • 显示编码就是你用软件打开5生成的数据,那个软件会用某个编码格式来解码。

要是出现乱码,只有可能是1和2的编码不兼容,或5和6的编码不兼容。就是你看到的是1那个格式的编码,但是你让编译器去解析是用另一个格式的编码去解析你看到的数据,所以会出错。或者你以某个格式输出那个二进制数据,然后你却用另一个格式去解析这个数据。

总之就是:IDEA显示给你看的是一种编码,这就是源文件编码,你看到的数据他的实质是二进制的数据。然后编译器会用一种编码来读你显示的数据生成的二进制数据。然后就是各种处理。然后结果你指定输出流编码格式识别jvm处理后的数据,输出到某处。最后你用软件并指定读数据的解码方式去读那个数据。注意源码中只有字符和字符串类型的数据是会对应编码表的,想什么int的11这样的数字是不对应编码表的。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值