Java 基本数据类型

8种基本数据类型

Java 共提供了八种基本数据类型:

  1. byte : 8 bit 有符号整数。最小值 -128(-2^7) ,最大值 127(2^7-1),默认值0。
  2. short:16 bit 有符号整数。最小值 -32768(-2^15),最大值 32767(2^15-1),默认值 0。
  3. int:32 bit 有符号整数。最小值 -2,147,483,648(-2^31),最大值 2,147,483,647 (2^31-1),默认值 0 L 。
  4. long:64 bit 有符号整数。 最小值 -2^63,最大值 2^63-1,默认值 0。
  5. float:32 bit 单精度浮点数。 最小值:1.4E-45,最大值:3.4028235E38,默认值 0.0F。
  6. double:64 bit 双精度浮点数。最小值:4.9E-324,最大值:1.7976931348623157E308,默认值 0.0D。
  7. boolean:1 bit,只有两个值 false 和 true。 默认值 false。
  8. char:一个单一的 16 bit Unicode 字符。

byte 的取值范围

在计算机中,bit 是存储数据的最小单元。一个 bit 也叫做一个二进制位。

byte 在Java中占有 8 bit 。且 byte 是有符号的整数位。取值范围为 -128 ~ 127 。那么这个取值范围是怎么来的呢?

计算机存储数值不是按照表面看到的数值来存储的。而是存在着 原码,反码和补码。在Java中使用补码的形式存储二进制数。

首先我们了解几个概念:机器数、真值、原码、反码、补码。

  • 机器数:在计算机中的二进制表示形式。机器数是带符号的。最高位存放符号,正数为0,负数为1。
  • 真值:因为机器数的最高位为符号位,那么我们看到的机器数跟在计算机中看到的数值是不一样的。例如:
    有符号数 1000 0011 ,其最高位为1,表示其是一个负数。而不是我们转换成十进制看到的 131 。所以为了区别起见,将带符号的机器数对应的真正数值称为机器数的真值。
    例如: 0000 0001的真值 = +000 0001 = +1
    1000 0001的真值 = - 000 0001 = -1
  • 原码:原码就是在符号位加上真值的绝对值,即用第一个为表示符号,其余位表示值。
    例如: +1 的原码 = 0000 0001
    - 1 的原码 = 1000 0001
  • 反码:正数的反码是其本身,负数的反码是在原码的基础上符号位不变,其余各个位逐位取反。
    例如:+1的原码 = 0000 0001 ,那么它的反码也是 0000 0001
    -1的原码 = 1000 0001 ,那么它的反码是 1111 1110
  • 补码:正数的补码是其本身,负数的补码是在反码的基础上 + 1。
    例如:+1的原码 = 0000 0001 ,那么它的反码也是 0000 0001,补码是 0000 0001
    -1的原码 = 1000 0001 ,那么它的反码是 1111 1110,补码是1111 1111

接下来我们看 byte 取值范围,在计算机中

  1. byte 占有 8 bit ,最高位符号位;
  2. 最大值应该是 0111 1111 ,其中最高位 0 代表 正数;转换为十进制为 127 ;
  3. 最小值为 1000 0000,其中最高位1 代表 负数。
  4. 由于负数是以补码的形式存在;将 最小值 1000 0000 先 -1转换成反码为 0111 1111 ,
  5. 然后在取反转换成原码为 1000 0000
  6. 将原码装换成十进制为 128 ,该数为负数,则最后为 -128。
  7. 即 byte的取值范围为 127 ~ -128 。

其实像short , int和long这样的整型计算取值范围也可以这样计算。

浮点值的二进制表示

在Java中浮点数的存储遵循了 IEEE754 标准。

单精度浮点数占用4个byte,32bit;双精度占用 8个byte,64bit。

根据 IEEE754 标准,任意一个二进制浮点数V可以表示成下面的形式:

  1. ![](https://cdn.nlark.com/yuque/__latex/a424988e5aa40f2b9674415ccae9f1e4.svg#card=math&code=V%20%3D%20%28-1%29%5Es%20%5Ctimes%20M%20%5Ctimes%202%5EE&height=29&width=193)
  1. Java基础数据类型的补充 - 图1 表示符号位,当s = 0,V为正数;当s = 1,V 为负数。
  2. M 表示有效数字,大于等于1,小于2。
  3. Java基础数据类型的补充 - 图2 表示指数位。其中叫做指数,或者 阶码。
  4. M 的取值范围一般为 Java基础数据类型的补充 - 图3 (二进制浮点数一般的都能写成 1.xxxx 的形式)

举例来说:
十进制的5.0 ,写成二进制是 101.0;相当于 Java基础数据类型的补充 - 图4 那么按上面的V的格式,可以得出 s = 0,M = 1.01,E = 2 ;
十进制的 - 5.0 ,写成二进制是 - 101.0;相当于Java基础数据类型的补充 - 图5 那么按上面的V的格式,可以得出 s = 1,M = 1.01,E = 2 ;
根据 IEEE754 标准,浮点数存储分为三个部分: 符号(sign),指数(exponet),有效数字(fraction),分别对应上面的
Java基础数据类型的补充 - 图6,E , M。图示如下:

Java基础数据类型的补充 - 图7

根据 IEEE754 标准,对于32位的浮点数,最高位是符号位 s,接着8位是指数E,剩下的 23 位为有效数字M。

Java基础数据类型的补充 - 图8

如上图,从右向左,给每个bit 编号,从 0-31。

第31位为符号位,第30-23位为指数位,第22-0位为有效数位。
单精度在内存的存储:

  • 第1位表示正负
  • 中间8位表示指数,采用补码的形式存储。
  • 后23位存储有效数位。

对于64位的浮点数,最高的1位是符号位 S ,接着的11位是指数E,剩下的52位为有效数字M。
Java基础数据类型的补充 - 图9

如上图,从右向左,给每个bit 编号,从 0-63。第63位为符号位,第62-52位为指数位,第51-0位为有效数位。
双精度在内存的存储:

  • 第1位表示正负
  • 中间11位表示指数,采用补码的形式存储。
  • 后52位存储有效数位。

根据 IEEE754 标准规定,对于有效数字M和指数E,还有一些特殊规定。
由于 Java基础数据类型的补充 - 图10 ,M可以写成 1.xxxx 的形式,其中 xxxx 表示小数部分,IEEE754 标准规定,在计算机内保存M时,默认这个数的第一个为1,因此可以被舍去,只保存后面的小数部分。比如保存1.01的时候,只保存 01,等到读取时,再把1加上去。这样做的目的,是节省1位有效数字。32位的浮点数,留给M只有23位,将第一位舍去后,等于可以保存 23 + 1= 24 位有效数字。那么64位的浮点数,留给M的只有52位,将第一位舍去后,等于可以保存 52 + 1= 53 位有效数字。

指数偏移值

关于指数 E ,它占有 8个bit,它是无符号的,可以表示 Java基础数据类型的补充 - 图11 个数即 0-255。但是科学计数法中的E是可以出现负数的。所以这里就引出了一个指数偏移值的概念。

指数偏移值(exponent bias),即浮点数表示法中指数域的编码值(即二进制的值),等于指数的实际值加上某个固定的值,IEEE 754 标准规定该固定值为Java基础数据类型的补充 - 图12,其中的 Java基础数据类型的补充 - 图13 存储指数的 bit 的长度。

对于8位的E,这个指数偏移值是 Java基础数据类型的补充 - 图14 ;对于11位的E,这个指数偏移值是 Java基础数据类型的补充 - 图15

采用指数的实际值加上固定的偏移值的办法表示浮点数的指数,好处是可以用长度为Java基础数据类型的补充 - 图16个bit的无符号整数来表示所有的指数取值,这使得两个浮点数的指数大小的比较更为容易,实际上可以按照字典序比较两个浮点表示的大小。

以 32位浮点数来说, 0 ~ 126 代表 -127 ~ -1; 127 代表 0 ; 128 ~ 255 代表 1 ~ 128 。 E的取值范围为 -126~127。

指数E还可以分为三种情况,看这个公式:

  1. ![](https://cdn.nlark.com/yuque/__latex/a424988e5aa40f2b9674415ccae9f1e4.svg#card=math&code=V%20%3D%20%28-1%29%5Es%20%5Ctimes%20M%20%5Ctimes%202%5EE&height=29&width=193)
  1. E不全为0或不全为1。这时,浮点数的指数E- 127得到真实值。再将有效数字M前面加上第一位的1。
  2. E全为0,这时,浮点数的指数E等于1-127,有效数字M不在机上第一位的1,而是还原为 0.xxxx 的小数。这样做是为了表示 Java基础数据类型的补充 - 图17 ,以及接近于0的很小的数字
  3. E全为1。这时,如果有效数字M全为0,表示 Java基础数据类型的补充 - 图18无穷大(Java基础数据类型的补充 - 图19取决于符号位);如果有效数字M不全为0,表示这个不是一个数(NaN)

比如, Java基础数据类型的补充 - 图20 的 E 是 10 ,要考虑偏移值 127 ,在计算机存储时会进行偏移计算 Java基础数据类型的补充 - 图21 ,即 1000 1001。
这样单精度浮点数的指数部分实际取值是从-126到127。

char 类型

在设计 char 类型时,是基于Unicode原始规范。占有16个bit,2个字节。取值范围为 U+0000 到 U+FFFF 。char 可以表示一个单独的字符,这个字符可以是一个字母,一个汉字,一个数字,一个标点符号,一个简单的空格,甚至一个表情符号,以及其它类似的内容。

什么是Unicode? 计算机内部都是 0 和 1,那么怎么显示字符呢?人们就设计一个字符编码表,给每个字符编一个编码,需要显示的时候就去编码表里去找对应的字符。Unicode就是其中一中字符编码的方式。 代码点 (code point): 简称码点,指在Unicode编码表中一个字符所对应的编号,该编码为唯一的。使用16进制表示 代码单元( code unit): 简称码元,编码的基本组成单位。对于UTF-8来说,码元是8bit;对于UTF-16来说,码元是16bit。换一种说法就是UTF-8的是以一个字节为最小单位的,UTF-16是以两个字节为最小单位的。

Java中 char 是固定的 16 bit 的长度,只要代码点在 U+0000 到 U+FFFF之间,都可以使用一个char完整的表示出一个字符。但是随着时代的发展,Unicode的长度早已今非昔比,Unicode的范围早已超出了 16 bit,现在的范围达到了 U+0000 到 U+10FFFF ,在Unicode中超出 U+0000 到 U+FFFF范围的字符叫做补充码点。那么如果需要表示的字符超出了char的范围怎么办呢?

举一个例子:码点 0x1F470 ,超出了 U+0000 到 U+FFFF 的范围,它在码表上是一个表情字符 👰。在 char 的包装类 Character 类,提供了一个方法 Character.charCount(codePoint) ,来判断当前的码点代表几个 char

  1. int codePoint = 0x1F470;
  2. System.out.println(Character.charCount(codePoint));

结果是 2 。
这个码点代表了两个 char 。两个char就可以组成一个char数组。在 Java 中字符串 String 就是使用char 数组表示,那么我们就可以很容易的猜到,在 Java 使用了 String 来表示这些补充码点。
现在我们知道了这个码点不能正常的使用char来表示,那么怎么将其转换成字符串呢?Character 提供了一个方法 Character.toChars(codePoint) ,来将这个补充码点来转换成char数组,继而我们就可以得到一个字符串了,通过下面的代码,我们就可以在控制台看到一个 👰 表情了。

  1. int codePoint = 0x1F470;
  2. System.out.println(Character.toChars(codePoint));

参考主要来自于阮一峰大神的博客和维基百科:
http://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
https://zh.wikipedia.org/wiki/IEEE_754
https://zh.wikipedia.org/wiki/單精度浮點數