细说Java基本数据类型之一的char类型与编码关系

打不倒的小乖兽 2021-08-12 15:19:44 浏览数 (3530)
反馈

一、开头

Java是一种强类型的语言,这意味着必须为每一个变量声明一种类型

Java中一共有8种基本数据类型(整形有4种,浮点型有2种,字符型1种,还有一种布尔类型)

由于Java程序必须保证在所有机器上都能得到相同的运行结果,所以各种数据类型的取值范围必须是固定的

二、整形

整形共有4种

  • byte:一个字节
  • short:2个字节
  • int:4个字节(刚好超过二十亿)
  • long int:8个字节

这里要注意的一些地方是

  • 长整形数值有一个后缀L或者l
  • 十六进制数值有一个前缀0x或者0X
  • 八进制有一个前缀0(容易混淆,不推荐使用)
  • 从Java7(JDK1.7)开始可以使用0b或者0B写二进制
  • 从JAVA7开始,还可以为数字字面量加下划线,如使用1_000_000表示100W(Java编译器会去除这些下划线)

在这里插入图片描述

开头已经说了,各种数据类型的取值范围必须是固定的,所以4种整形的范围都为有符号位的范围,Java也因此没有Unsigned符号。

所以针对Unsigned的整形,基本数据类型的包装类有对应的API的

这里首先要认识的一点是,只要不溢出,加法、减法和乘法都能正常计算,但除法是会出问题的

三、Byte.toUnsignedInt

这个API的功能是针对Unsigned的Byte的转化成Unsigned

在这里插入图片描述

从源码上看,其实就是将其强制转换成int,相当于扩大了位数,然后通过与上0xff,0xff是十六进制,转化成二进制就是11111111,这个与运算的作用其实为了限制位数,因为byte是1个字节,顶多只有8位,超过8位的那些都不要,对于Unsigned来说,应该都为0.

四、Integer.divideUnsigned

这个API的功能是针对Unsigned的int类型除法的

在这里插入图片描述
在这里插入图片描述

可以看到,他的处理与Byte一样,都是转化成更高位的类型,这里转化成long,然后通过与运算舍弃后面多出来的位数(其实是改为0)

五、Integer.remainderUnsigned

这个是用来求余数的

在这里插入图片描述

可以看到同样也是转化成更高位去处理

六、Long.divideUnsigned

现在Long没有更高位了怎么办呢?

下面是源码

 public static long divideUnsigned(long dividend, long divisor) {
        //divisor是除数
        //而divident是被除数
        
        //首先判断除数是否为Unsigned(<0就代表为unsigned,只不过将符号位看成1,变为负数)
        if (divisor < 0L) { // signed comparison
            // Answer must be 0 or 1 depending on relative magnitude
            // of dividend and divisor.
            
            //可以看到这里的返回值只有0和1
            //这是因为除数为unsigned,根据整形的向下取整规则
            //得到的结果只能为1和0(dividend大于divisor就为1,小于就为0)
            //dividend不可能为divisor的两倍(因为位数不过)
            return (compareUnsigned(dividend, divisor)) < 0 ? 0L :1L;
        }

        //如果除数不是Unsigned,那么就判断被除数
        
        if (dividend > 0) //  Both inputs non-negative
            //如果被除数不是Unsigned,就直接除就好
            return dividend/divisor;
        else {
            /*
             * For simple code, leveraging BigInteger.  Longer and faster
             * code written directly in terms of operations on longs is
             * possible; see "Hacker's Delight" for divide and remainder
             * algorithms.
             */
             //如果是,那么就将除数和被除数换成更高位的BigInt型,去进行
           
            return toUnsignedBigInteger(dividend).
                divide(toUnsignedBigInteger(divisor)).longValue();
        }
    }

下面我们就来看看compareUnsigned方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

MIN_VALUE是代表长整形可以取的最小值,也就是 − 2 63 -2^{63} −263

可以看到,具体的过程就是让两个数减去最小值,然后进行比较

原理是,即使是Unsigned,只要减去了最小值,就不会超过有符号位的范围,然后通过比较减去后的大小,就可以判断除数和被除数谁大谁小,然后就返回0和1。

七、浮点型

浮点型有两种类型,一种为float,一种为double。

这里,我们认识一下精度损失

在两种浮点型,小数都是使用二进制表示的,比如 2 − 1 或 者 2 − 2 2^{-1}或者2^{-2} 2−1或者2−2,也就是0.5,0.125这些,也就是说,有一些小数是无法使用二进制表示的,只能通过后面的位数进行无限逼近,所以就会产生精度损失。

那什么是双精度和单精度呢?

这是根据double和float的位数来区分的,double为8字节,而float为4字节,所以double可以使用更多位数进行逼近,所以double会更加精确。

这里要注意的是,所有的浮点数计算都遵循IEEE754规范

对于表示溢出和出错情况,使用了三个特殊的浮点数值去表示

  • 正无穷大
  • 负无穷大
  • NaN(不是一个数字)

2021051411162054

八、字符型

char类型本来用来表示单个字符,但如今有些Unicode字符可以用一个char表示,也就是两个字节,但有时一些Unicode字符需要用多个字节表示,也就是使用多个char表示

char类型的值可以表示为十六进制值,从u0000~uffff。这里是u充当了一个转义序列的功能,同时u转义序列是可以出现在字符常量或字符串,所以使用注释和参数的时候,要注意一下

在Java中,char类型描述了UTF-16编码中的一个代码单元

九、Unicode

在认识UTF-16前,我们需要认识Unicode

Unicode其实相当于一本很厚的字典,里面储存了世界上所有语言的字符,使用Unicode码点唯一地对应一个字符。

Unicode是没有规定字符对应的二进制码占用的空间是多少,那么问题来了,以“汉”字为例,它的Unicode码点为0x6c49,对应的二进制为110110001001001,也就是15位二进制,也就说明了,这个字需要用2个字节去存储这个字,那么,对于其他字体,很有可能出现3个字节,或者更多的字节去存储,对于计算机来说,计算机怎么知道这两个字节表示的是一个字符,而不是与后面的字节形成一个字符?

所以,为了解决Unicode的这个问题,新的编码方式UTF-8、UTF-16和UTF-32就出现了

十、UTF-8

UTF其实是Unicode Transformation Format的缩写,即统一Unicode编码转换格式

UTF-8的特点就是可变长,即对于不同长度字节的字符有很好的兼容性

编码规则如下

对于单个字节的字符(也就是基本字符),也就是8位,会将第一位设为0,后面的七位会对应这个字符的Unicode码点,因此对于0~ 2 7 2^7 27号字符是完全可以的,甚至与ASCII(另一种编码方式,只不过不支持中文只有英文和符号)完全相同(这时候可能会有人说那么对于 2 8 至 2 7 2^8至2^7 28至27里面的字符呢?其实这一段被分在了使用2个字节表示)

对于需要使用N个字节来表示的字符(N>1),第一个字节的前N位都设为1,第N+1位设为0(用来记录这个字符是用多少个字节来存储的,让计算机可以识别出),剩余后面的N-1个字节的前两位都要设置为10,剩下的二进制位则使用这个字符的Unicode码点来进行补充

Unicode十六进制码点范围 UTF-8二进制
0000 0000 ~ 0000 007F(注意这里只有7位) 0xxxxxxx(对应表示码点的七位)
0000 0080 ~ 0000 07FF(注意这里为11位) 110xxxxx 10xxxxxx (对应码点11位)
0000 0800 ~ 0000 FFFF(注意这里位16位) 1110xxxx 10xxxxxx 10xxxxxx(对应码点16位)
0001 0000 ~ 0010 FFFF(这里为18位) 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx(虽然超过码点位数,但不影响表示)

0000 0800 ~ 0000 FFFF(注意这里位16位)1110xxxx 10xxxxxx 10xxxxxx(对应码点16位)0001 0000 ~ 0010 FFFF(这里为18位)11110xxx 10xxxxxx 10xxxxxx 10xxxxxx(虽然超过码点位数,但不影响表示)

通过表格,可以看到UTF-8d的编码其实很简单,下面还是以"汉"为例,具体说一下如何进行UTF-8的编码和解码的

汉的编码为0x00006c49,对应在Unicode十六进制码点范围的第三行,所以对应的UTF-8二进制为 1110 x x x x 10 x x x x x x 10 x x x x x x 1110xxxx 10xxxxxx 10xxxxxx 1110xxxx10xxxxxx10xxxxxx,然后将0x0006c49变为二进制为0x0110110001001001,然后填入到x里面即可(从第最后一位开始),结果为111001101011000110001001,然后再转换成十六进制为:0xE6 0xB7 0x89

解码的过程也十分简单:专为二进制之后,先判断多少个字节,如果第一个字节的第一位是0,则代表是只有1和字节,如果不是,就判断前面总共有多少个1就碰到0,多少个1就是多少个字节,通过知道多少个字节,就可以知道后面要读多少个字节来对应这个字符,只要去掉开头的10就行

十一、UTF-16

在认识UTF-16之前,先认识平面这个东西

前面提到过Unicode编码是一本很厚的字典,将全世界的字符都定义在这个集合里面,但这本字典不是一次性完成的,而是经过持续地收集才完成的,所以也就产生了分区,进行分区定义。每个区可以存放65536,也就是2^16字符,一个区就称为一个平面。目前Unicode一共有17(2 ^ 4+1)个平面(剩余16个为辅助平面),所以整个Unicodez字符集大小为 2 21 2^{21} 221

第一个平面,也就是第一个区,被称为基本平面,前 2 16 2^{16} 216个字符也就被成为基本字符,码点范围也就是从0~ 2 16 − 1 2^{16}-1 216−1,写成十六进制就是从U+0000到U+FFFF,最常见的字符大多都在这个区了。那么剩余的17个区,对应的码点就是从U+10000到U+10FFFF(刚好16倍),那么要如何解决确定字符与字节对应的问题呢?

基本平面有一个很巧妙的地方,在基本平面内,从U+D800到U+DFFF是一个空段,也就是再这个区间内的码点是没有对应任何字符的,因此UTF-16就利用了这个空段来做了一个映射辅助平面的码点(利用基本平面来储存辅助平面)

在辅助平面码点对应的字符总共有 2 20 2^{20} 220个,所以至少需要20个二进制位才可以完全对应辅助平面码点的字符。

UTF-16将这20个二进制位分成一半,前十位映射在U+D800到U+DBFF之间(称为高位),后10位映射在U+DC00到U+DFFF之间(成为低位),所以当遇到多个字节时,如果发现有码点位于这两个段区间,这就意味着这是辅助平面码点的映射,辅助平面字符被拆分成多个基本平面的码点表示

据个栗子

汉字"?“的 Unicode 码点为 0x20BB7,该码点显然超出了基本平面的范围(0x0000 - 0xFFFF),因此需要使用四个字节表示。首先用 0x20BB7 - 0x10000 计算出超出的部分,然后将其用 20 个二进制位表示(不足前面补 0 ),结果为0001000010 1110110111。接着,将前 10 位映射到 U+D800 到 U+DBFF 之间,后 10 位映射到 U+DC00 到 U+DFFF 即可。U+D800 对应的二进制数为 1101100000000000,直接填充后面的 10 个二进制位即可,得到 1101100001000010,转成 16 进制数则为 0xD842。同理可得,低位为 0xDFB7。因此得出汉字”?"的 UTF-16 编码为 0xD842 0xDFB7

现在我们回到字符型里面的重点在Java中,char类型描述了UTF-16编码中的一个代码单元,可以知道char类型采用的是utf-16编码方式,代码单元其实指的就是U+D800~U+DBFF和U+DC00~U+DFFF这两个映射区(这里是两个个代码单元,一个char只能使用一个代码单元,不过通常一个代码单元能表示绝大多数的字符了,但也是因为这个原因,有些字符char不可以完整表示),通过这个代码单元,就可以进行解码获取Unicode编码了

十二、布尔类型

boolean类型有两个值,false和true,用来判断逻辑条件,整形值和布尔值之间是不能互换的

到此这篇关于Java数据类型之细讲char类型与编码关系的文章就介绍到这了,更多相关Java基本数据类型的内容,请搜索W3Cschool以前的文章或继续浏览下面的相关文章,希望大家以后多多支持!


2 人点赞