基本数据类型【整数类型】【浮点类型】【字符类型】【布尔类型】

@


源码:
Gitee https://gitee.com/drip123456/java-se
GIthub https://github.com/Drip123456/JavaSE

专栏: JavaSE笔记专栏

基本数据类型

我们的程序中可能需要表示各种各样的数据,比如整数、小数、字符等等,这一部分我们将探索Java中的八大基本数据类型。只不过在开始之前,我们还需要先补充一点简单的计算机小知识。

计算机中的二进制表示

在计算机中,所有的内容都是二进制形式表示。十进制是以10为进位,如9+1=10;二进制则是满2进位(因为我们的计算机是电子的,电平信号只有高位和低位,你也可以暂且理解为通电和不通电,高电平代表1,低电平代表0,由于只有0和1,因此只能使用2进制表示我们的数字!)比如1+1=10=2^1+0,一个位也叫一个bit,8个bit称为1字节,16个bit称为一个字,32个bit称为一个双字,64个bit称为一个四字,我们一般采用字节来描述数据大小。

注意这里的bit跟我们生活中的网速MB/s是不一样的,小b代表的是bit,大B代表的是Byte字节(8bit = 1Byte字节),所以说我们办理宽带的时候,100Mbps这里的b是小写的,所以说实际的网速就是100/8 = 12.5 MB/s了。

十进制的7 -> 在二进制中为 111 = 2^2 + 2^1 + 2^0

现在有4个bit位,最大能够表示多大的数字呢?

  • 最小:0000 => 0
  • 最大:1111 => 2^3 + 2^2 + 2^1 + 2^0 => 8 + 4 + 2 + 1 = 15

在Java中,无论是小数还是整数,他们都要带有符号(和C语言不同,C语言有无符号数)所以,首位就作为我们的符号位,还是以4个bit为例,首位现在作为符号位(1代表负数,0代表正数):

  • 最小:1111 => -(2^2 + 2^1 + 2^0) => -7
  • 最大:0111 => +(2^2 + 2^1 + 2^0) => +7 => 7

现在,我们4bit能够表示的范围变为了-7~+7,这样的表示方式称为原码。虽然原码表示简单,但是原码在做加减法的时候,很麻烦!以4bit位为例:

1+(-1) = 0001 + 1001 = 怎么让计算机去计算?(虽然我们知道该去怎么算,但是计算机不知道!)

我们得创造一种更好的表示方式!于是我们引入了反码

  • 正数的反码是其本身
  • 负数的反码是在其原码的基础上, 符号位不变,其余各个位取反

经过上面的定义,我们再来进行加减法:

1+(-1) = 0001 + 1110 = 1111 => -0 (直接相加,这样就简单多了!)

思考:1111代表-0,0000代表+0,在我们实数的范围内,0有正负之分吗?0既不是正数也不是负数,那么显然这样的表示依然不够合理!根据上面的问题,我们引入了最终的解决方案,那就是补码,定义如下:

  • 正数的补码就是其本身 (不变!)
  • 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1(即在反码的基础上+1,此时1000表示-8)
  • 对补码再求一次补码就可得该补码对应的原码。

比如-7原码为1111,反码为1000,补码就是1001了,-6原码为1110,反码为1001,补码就是1010。所以在补码下,原本的1000就作为新增的最小值-8存在。

所以现在就已经能够想通,-0已经被消除了!我们再来看上面的运算:

1+(-1) = 0001 + 1111 = (1)0000 => +0 (现在无论你怎么算,也不会有-0了!)

所以现在,1111代表的不再是-0,而是-1,相应的,由于消除-0,负数多出来一个可以表示的数(1000拿去表示-8了),那么此时4bit位能够表示的范围是:-8~+7(Java使用的就是补码!)在了解了计算机底层的数据表示形式之后,我们再来学习这些基本数据类型就会很轻松了。

整数类形

整数类型是最容易理解的类型!既然我们知道了计算机中的二进制数字是如何表示的,那么我们就可以很轻松的以二进制的形式来表达我们十进制的内容了。

在Java中,整数类型包括以下几个:

  • byte 字节型 (8个bit,也就是1个字节)范围:-128~+127
  • short 短整形(16个bit,也就是2个字节)范围:-32768~+32767
  • int 整形(32个bit,也就是4个字节)最常用的类型:-2147483648 ~ +2147483647
  • long 长整形(64个bit,也就是8个字节)范围:-9223372036854775808 ~ +9223372036854775807

这里我们来使用一下,其实这几种变量都可以正常表示整数:

public static void main(String[] args) {
    short a = 10;
    System.out.println(a);
}

因为都可以表示整数,所以说我们可以将小的整数类型值传递给大的整数类型:

public static void main(String[] args) {
    short a = 10;
    int b = a;   //小的类型可以直接传递给表示范围更大的类型
    System.out.println(b);
}

反之会出现报错:

这是由于我们在将小的整数类型传递给大的整数类型时发生了隐式类型转换,只要是从存储范围小的类型到存储范围大的类型,都支持隐式类型转换,它可以自动将某种类型的值,转换为另一种类型,比如上面就是将short类型的值转换为了int类型的值。

隐式类型转换不仅可以发生在整数之间,也可以是其他基本数据类型之间,我们后面会逐步介绍。

实际上我们在为变量赋一个常量数值时,也发生了隐式类型转换,比如:

public static void main(String[] args) {
   byte b = 10;    //这里的整数常量10,实际上默认情况下是int类型,但是由于正好在对应类型可以表示的范围内,所以说直接转换为了byte类型的值
}

按照long类型的规定,实际上是可以表示这么大的数字的,但是为什么这里报错了呢?这是因为我们直接在代码中写的常量数字,默认情况下就是int类型,这么大肯定是表示不下的,如果需要将其表示为一个long类型的常量数字,那么需要在后面添加大写或是小写的L才可以。

public static void main(String[] args) {
    long a = 922337203685477580L;   //这样就可以正常编译通过了
}

当然,针对于这种很长的数字,为了提升辨识度,我们可以使用下划线分割每一位:

public static void main(String[] args) {
   int a = 1_000_000;    //当然这里依然表示的是1000000,没什么区别,但是辨识度会更高
}

我们也可以以8进制或是16进制表示一个常量值:

public static void main(String[] args) {
    System.out.println(0xAF);
    System.out.println(032);
}
  • 十六进制:0x开头的都是十六进制表示法,十六进制满16进一,但是由于我们的数学只提供了0-9这十个数字,10、11、12...15该如何表示呢,我们使用英文字母A按照顺序开始表示,A表示10、B表示11...F表示15。比如上面的0xA实际上就是我们十进制中的10。
  • 八进制:以0开头的都是八进制表示法,八进制就是满8进一,所以说只能使用0-7这几个数字,比如上面的012实际上就是十进制的10。

我们最后再来看一个问题:

public static void main(String[] args) {
    int a = 2147483647;   //int最大值
    a = a + 1;   //继续加
    System.out.println(a);
}

此时a的值已经来到了int类型所能表示的最大值了,那么如果此时再继续+1,各位小伙伴觉得会发生什么?可以看到结果很奇怪:

在这里插入图片描述

什么情况???怎么正数加1还变成负数了?请各位小伙伴回想一下我们之前讲解的原码、反码和补码。

我们先来看看,当int为最大值时,二进制表示形式为什么:

  • 2147483647 = 01111111 11111111 11111111 11111111(第一个是符号位0,其他的全部为1,就是正数的最大值)

那么此时如果加1,会进位成:

  • 10000000 00000000 00000000 00000000

各位想一想,符号位为1,那么此时表示的不就是一个负数了吗?我们回想一下负数的补码表示规则,瞬间就能明白了,这不就是补码形式下的最小值了吗?

所以说最后的结果就是int类型的最小值:-2147483648,是不是感觉了解底层原理会更容易理解这是为什么。

浮点类型

前面我们介绍了整数类型,我们接着来看看浮点类型,在Java中也可以轻松地使用小数。

首先来看看Java中的小数类型包含哪些:

  • float 单精度浮点型 (32bit,4字节)
  • double 双精度浮点型(64bit,8字节)

那么小数在计算机中又是如何存放的呢?相较于整数,要稍微复杂一点:
在这里插入图片描述

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

\[V = (-1)^S \times M \times 2^E \]

  • \((-1)^S\) 表示符号位,当 S=0,V 为正数;当 S=1,V 为负数。
  • M 表示有效数字,大于等于 1,小于 2,但整数部分的 1 不变,因此可以省略。(例如尾数为1111010,那么M实际上就是1.111010,尾数首位必须是1,1后面紧跟小数点,如果出现0001111这样的情况,去掉前面的0,移动1到首位,随着时间的发展,IEEE 754标准默认第一位为1,故为了能够存放更多数据,就舍去了第一位,比如保存1.0101 的时候, 只保存 0101,这样能够多存储一位数据)
  • \(2^E\) 表示指数位。(用于移动小数点,所以说才称为浮点型)

比如, 对于十进制的 5.25 对应的二进制为:101.01,相当于:\(1.0101 \times 2^2\)。所以,S 为 0,M 为 1.0101,E 为 2。因此,对于浮点类型,最大值和最小值不仅取决于符号和尾数,还有它的阶码,所以浮点类型的大致取值范围:

  • 单精度:\(±3.40282347 \times 10^{38}\)
  • 双精度:\(±1.79769313486231570 \times 10^{308}\)

我们可以直接创建浮点类型的变量:

public static void main(String[] args) {
    double a = 10.5, b = 66;   //整数类型常量也可以隐式转换到浮点类型
}

注意,跟整数类型常量一样,小数类型常量默认都是double类型,所以说如果我们直接给一个float类型赋值:

在这里插入图片描述

由于float类型的精度不如double,如果直接给其赋一个double类型的值,会直接出现错误。

同样的,我们可以给常量后面添加大写或小写的F来表示这是一个float类型的常量值:

public static void main(String[] args) {
    float f = 9.9F;   //这样就可以正常编译通过了
}

但是反之,由于double精度更大,所以说可以直接接收float类型的值:

public static void main(String[] args) {
    float f = 9.9F;
    double a = f;    //隐式类型转换为double值
    System.out.println(a);
}

只不过由于精度问题,最后的打印结果:

在这里插入图片描述

这种情况是正常的,因为浮点类型并不保证能够精确计算,我们会在下一章介绍 BigDecimal 和 BigInteger,其中BigDecimal更适合需要精确计算的场景。

我们最后来看看下面的例子:

public static void main(String[] args) {
    long l = 21731371236768L;
    float f = l;   //这里能编译通过吗?
    System.out.println(f);
}

此时我们发现,long类型的值居然可以直接丢给float类型隐式类型转换,很明显float只有32个bit位,而long有足足64个,这是什么情况?怎么大的还可以隐式转换为小的?这是因为虽然float空间没有那么大,但是由于是浮点类型,指数可以变化,最大的数值表示范围实际上是大于long类型的,虽然会丢失精度,但是确实可以表示这么大的数。

所以说我们来总结一下隐式类型转换规则:byte→short(char)→int→long→float→double

字符类型

字符类型也是一个重要的基本数据类型,它可以表示计算机中的任意一个字符(包括中文、英文、标点等一切可以显示出来的字符)

  • char 字符型(16个bit,也就是2字节,它不带符号)范围是0 ~ 65535

可以看到char类型依然存储的是数字,那么它是如何表示每一个字符的呢?实际上每个数字在计算机中都会对应一个字符,首先我们需要介绍ASCII码:

在这里插入图片描述

比如我们的英文字母A要展示出来,那就是一个字符的形式,而其对应的ASCII码值为65,所以说当char为65时,打印出来的结果就是大写的字母A了:

public static void main(String[] args) {
    char c = 65;
    System.out.println(c);
}

得到结果为:

在这里插入图片描述

或者我们也可以直接写一个字符常量值赋值:

public static void main(String[] args) {
    char c = 'A';    //字符常量值需要使用单引号囊括,并且内部只能有一个字符
    System.out.println(c);
}

这种写法效果与上面是一样的。

不过,我们回过来想想,这里的字符表里面不就128个字符吗,那char干嘛要两个字节的空间来存放呢?我们发现表中的字符远远没有我们所需要的那么多,这里只包含了一些基础的字符,中文呢?那么多中文字符(差不多有6000多个),用ASCII编码表那128个肯定是没办法全部表示的,但是我们现在需要在电脑中使用中文。这时,我们就需要扩展字符集了。

我们可以使用两个甚至多个字节来表示一个中文字符,这样我们能够表示的数量就大大增加了,GB2132方案规定当连续出现两个大于127的字节时(注意不考虑符号位,此时相当于是第一个bit位一直为1了),表示这是一个中文字符(所以为什么常常有人说一个英文字符占一字节,一个中文字符占两个字节),这样我们就可以表示出超过7000种字符了,不仅仅是中文,甚至中文标点、数学符号等,都可以被正确的表示出来。

不过这样能够表示的内容还是不太够,除了那些常见的汉字之外,还有很多的生僻字,比如龘、錕、釿、拷这类的汉字,后来干脆直接只要第一个字节大于127,就表示这是一个汉字的开始,无论下一个字节是什么内容(甚至原来的128个字符也被编到新的表中),这就是Windows至今一直在使用的默认GBK编码格式。

虽然这种编码方式能够很好的解决中文无法表示的问题,但是由于全球还有很多很多的国家以及很多很多种语言,所以我们的最终目标是能够创造一种可以表示全球所有字符的编码方式,整个世界都使用同一种编码格式,这样就可以同时表示全球的语言了。所以这时就出现了一个叫做ISO的(国际标准化组织)组织,来定义一套编码方案来解决所有国家的编码问题,这个新的编码方案就叫做Unicode(准确的说应该是规定的字符集,包含了几乎全世界所有语言的字符),规定每个字符必须使用两个字节,即用16个bit位来表示所有的字符(也就是说原来的那128个字符也要强行用两位来表示)

但是这样的话实际上是很浪费资源的,因为这样很多字符都不会用到两字节来保存,肯定不能直接就这样去表示,这会导致某些字符浪费了很多空间,我们需要一个更加好用的具体的字符编码方式。所以最后就有了UTF-8编码格式(它是Unicode字符集的一个编码规则),区分每个字符的开始是根据字符的高位字节来区分的,比如用一个字节表示的字符,第一个字节高位以“0”开头;用两个字节表示的字符,第一个字节的高位为以“110”开头,后面一个字节以“10开头”;用三个字节表示的字符,第一个字节以“1110”开头,后面俩字节以“10”开头;用四个字节表示的字符,第一个字节以“11110”开头,后面的三个字节以“10”开头:

10000011 10000110    //这就是一个连续出现都大于127的字节(注意这里是不考虑符号位的)

所以如果我们的程序需要表示多种语言,最好采用UTF-8编码格式,随着更多的字符加入,实际上两个字节也装不下了,可能需要3个甚至4个字节才能表示某些符号,后来就有了UTF-16编码格式,Java在运行时采用的就是UTF-16,几乎全世界的语言用到的字符都可以表示出来。

Unicode符号范围(十六进制) UTF-8编码方式(二进制)
0000 0000 ~ 0000 007F 0xxxxxxx
0000 0080 ~ 0000 07FF 110xxxxx 10xxxxxx
0000 0800 ~ 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000 ~ 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

注意:Unicode 是“字符集”,也就是有哪些字符,而UTF-8、UTF-16 是“编码规则”,也就是怎么对这些字符编码,怎么以二进制的形式保存,千万不要搞混了。

简而言之,char实际上需要两个字节才能表示更多种类的字符,所以,char类型可以直接表示一个中文字符:

public static void main(String[] args) {
    int a = '淦';   //使用int类型接收字符类型常量值可以直接转换为对应的编码
    System.out.println(a);
}

Java程序在编译为.class文件之后,会采用UTF-8的编码格式,支持的字符也非常多,所以你甚至可以直接把变量名写成中文,依然可以编译通过

介绍完了字符之后,我们接着来看看字符串,其实字符串我们在一开始就已经接触到了。字符虽然可以表示一个中文,但是它没办法表示多个字符:

但是实际上我们使用率最高的还是多个字符的情况,我们需要打印一连串的字符。这个时候,我们就可以使用字符串了:

public static void main(String[] args) {
    String str = "啊这";    //字符串需要使用双引号囊括,字符串中可以包含0-N个字符
}

注意,这里使用的类型是String类型,这种类型并不是基本数据类型,它是对象类型,我们会在下一章继续对其进行介绍,这里我们只需要简单了解一下就可以了。

布尔类型

布尔类型是Java中的一个比较特殊的类型,它并不是存放数字的,而是状态,它有下面的两个状态:

  • true - 真
  • false - 假

布尔类型(boolean)只有truefalse两种值,也就是要么为真,要么为假,布尔类型的变量通常用作流程控制判断语句(不同于C语言,C语言中一般使用0表示false,除0以外的所有数都表示true)布尔类型占据的空间大小并未明确定义,而是根据不同的JVM会有不同的实现。

public static void main(String[] args) {
    boolean b = true;   //值只能是true或false
    System.out.println(b);
}

如果给一个其他的值,会无法编译通过:

在这里插入图片描述

至此,基本数据类型的介绍就结束了。

posted @ 2024-02-28 22:24  笠大  阅读(12)  评论(0编辑  收藏  举报