1:数据在计算机中是如何存储的
二进制,八进制,十六进制
二进制是计算技术中广泛采用的一种数制。所谓的二进制就是指由 0,1 两个数码表示的数。进位规则为: “逢二进一”。
计算机运算基础采用的就是二进制。原因在于:电子晶体管有两种基本状态,开和关,对应表示为 0 和 1。
常用的进制还有 八进制以及十六进制,在电脑科学中,经常会用到 二进制与十六进制。
如何使用 Java 语言表示一个二进制,八进制与十六进制的数呢?
二进制表示:
int a = 0b11;
在数字前加上 0b 则表示一个二进制数,该数字的十进制表示为:3
八进制表示:
int a = 011;
在数字前加上 0 则表示一个八进制数,该数字的十进制表示为:9
十六进制表示:
int a = 0x11;
在数字前加上 0x 则表示一个十六进制数,该数字的十六进制表示为:17
同样的,我们也可以使用程序将十进制数字表示为二进制,八进制,十六进制的形式
public class Main {
public static void main(String[] args) {
int a = 10;
System.out.println(Integer.toBinaryString(a)); // 转换为二进制的字符串形式
System.out.println(Integer.toHexString(a)); // 转换为十六进制的字符串形式
System.out.println(Integer.toOctalString(a)); // 转换为八进制的字符串形式
}
}
字节
字节(Byte)是计算机信息技术用于计量存储容量的一种计量单位,一个字节存储 8 位无符号二进制数字1 Byte = 8 bit
ASCII 码
在计算机中,所有的数据在存储和运算时都要使用二进制数来表示。
例如数字 32 的 十六进制表示为:20;二进制表示为:0010 0000
但是,除了数字以外,还有字母,空格等特殊符号需要被表示出来,而具体使用哪些二进制数来表示哪些符号,就要制定出一套编码规范为了大家相互通信时不造成混乱,那么大家必须使用这套编码规则。于是美国相关的标准化组织就推出了 ASCII 编码,既定了一套规范,到目前为止一共定义了 128 个字符。
在 Mac 或 Linux 操作系统的终端,我们可以输入命令:
man ascii
来查看 ASCII 码表
比较常见的有:
- 65 表示为 A
- 97 表示为 a
2:基本数据类型
Java 中有两种数据类型
- 原生(基本)数据类型
- 引用数据类型
Java 的基本数据类型有八种
- byte
- short
- int
- long
- float
- double
- char
- boolean
基本数据类型的声明
public class Main {
public static void main(String[] args) {
byte b = 1;
short s = 1;
int i = 1;
long l = 1L; // 一般我们会使用大写的 L 作为后缀表示,因为小写的 l 不易区分
float f = 0.1f;
double d = 0.1;
char c = '1';
boolean flag = true;
}
}
Java 语言中也提供了几种利于表示数字的表示机制,比如我们可以使用 “_” 来分割数字,这样我们就可以清晰地知道一个大数是多少
public class Main {
public static void main(String[] args) {
int a = 10_0000_0000; // 我们可以很清晰地知道 该数字为 10 亿
}
}
同时,也有科学记数法表示:
public class Main {
public static void main(String[] args) {
int a = (int) 1e8; // 该数字为 1 亿
}
}
各个基本类型的存储范围
它们表示的存储范围,我们可以通过各个基本类型对应的包装类的 MIN_VALUE 和 MAX_VALUE 得知:
public class Main {
public static void main(String[] args) {
System.out.println("byte 存储范围:" +Byte.MIN_VALUE+" ~ " +Byte.MAX_VALUE);
System.out.println("short 存储范围:" +Short.MIN_VALUE+" ~ " +Short.MAX_VALUE);
System.out.println("int 存储范围:" +Integer.MIN_VALUE+" ~ " +Integer.MAX_VALUE);
System.out.println("long 存储范围:" +Long.MIN_VALUE+" ~ " +Long.MAX_VALUE);
System.out.println("float 存储范围:" +Float.MIN_VALUE+" ~ " +Float.MAX_VALUE);
System.out.println("double 存储范围:" +Double.MIN_VALUE+" ~ " +Double.MAX_VALUE);
System.out.println("char 能够存储一个字符,消耗两个字节的空间");
System.out.println("boolean 只能表示 true 和 false");
}
}
程序输出结果为:
byte 存储范围:-128 ~ 127
short 存储范围:-32768 ~ 32767
int 存储范围:-2147483648 ~ 2147483647
long 存储范围:-9223372036854775808 ~ 9223372036854775807
float 存储范围:1.4E-45 ~ 3.4028235E38
double 存储范围:4.9E-324 ~ 1.7976931348623157E308
char 能够存储一个字符,消耗两个字节的空间
boolean 只能表示 true 和 false
如果我们声明的变量数值大小超出了声明它的数据类型范围,就会出现溢出。
关于浮点数
来看这个程序:
public class Main {
public static void main(String[] args) {
System.out.println(0.1 + 0.2);
}
}
该程序在我的主机输出的结果为:
0.30000000000000004
出现这个结果的原因是因为,计算机中浮点数的存储为近似值。反应到真实项目中,我们要知道,金额是绝对不可以使用浮点数来表示的,因为会出现精度丢失。
一般情况,金额的计算我们会使用 BigDecimal 类型,如代码所示:
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("0.1"); // 一定要使用字符串,或这种形式:BigDecimal.valueOf(0.1);
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b));
}
}
程序输出结果:
0.3
3:类型转换与类型提升
类型转换
类型转换原则:
- 低精度可以直接转换为高精度
- 高精度可以强制转换成低精度,但是要自己承担溢出的风险
来看示例:
public class Main {
public static void main(String[] args) {
byte a = 1;
int b = a; // 低精度可以直接转换为高精度
int c = 1;
byte d = (byte) c; // 高精度强制转换为低精度,但是要承担溢出风险
}
}
类型提升
什么是类型提升?当不同精度的数据进行计算的时候,得到的结果的类型会提升到参与计算的数据里面最高的精度
示例程序:
public class Main {
public static void main(String[] args) {
short a = 1;
int b = 1;
double c = 0.1;
double d = a + b + c; // short,int,double 这几种类型中, double的精度最高,结果的数据类型即 double
}
}
我们知道,Java 语言中的除法为 “地板除”,譬如:3 / 2
,该运算结果为:1
我们如果想获得带小数结果,可以使用类型转换与类型提升两种方法:
类型转换:
public static double divide(int a, int b) {
int res = a / b;
return (double) res;
}
类型提升:
public static double divide(int a, int b) {
return (double) a / b; // 先将 a 强制转换为 double 类型,然后用 double 类型数字与int类型数字计算,结果为 double
}
4:基本数据类型对应的装箱类型
基本数据类型与对应的装箱类型:
基本数据类型 装箱类型 byte Byte short Short int Integer long Long float Float double Double char Character boolean Boolean
为什么要有装箱类型?
我们知道 Java 中只有两种数据类型,第一种是基本数据类型,第二种是引用类型;引用类型声明的是一个对象,保存在堆内存中。
装箱的目的:
- 装箱将一个基本数据类型包装成一个对象,一个类的对象就可以有很多可以调用的方法,便于我们进行操作
- 泛型不支持基本类型,例如:
List<Integer> list = new ArrayList<>();
- 装箱类型可以指明为 null,我们可以这样声明:
Integer i = null;
,反之,基本类型是不具有这个特点的
自动装箱与自动拆箱
从 JDK1.5 开始,Java 语言提供了自动装拆箱的机制
public class Main {
public static void main(String[] args) {
int a = 1;
Integer integer = a; // 自动装箱
int b = a; // 自动拆箱
}
}
如程序所示,自动装箱就是自动将基本数据类型转换为包装类型;自动拆箱就是自动将包装类型转换为基本数据类型。
5:== 与 equals 约定在数据类型中的应用
“==” 和 equals 有什么区别?
- “==” 是判断两个变量或实例指向的是否是同一块内存空间,也就是说两个变量或实例是否相同
- equals 则是判断两个变量或实例指向的内存空间的值是否相等,这里面“相等”的规则是由自己定义的
用一张图可以简要说明 “==” 和 equals 的区别:
我们来看一个示例并进行说明
程序一:
public class Main {
public static void main(String[] args) {
int a = 1000;
int b = 1000;
System.out.println(a == b);
}
}
该程序输出的结果为 true
程序二:
public class Main {
public static void main(String[] args) {
Integer a = 1000;
Integer b = 1000;
System.out.println(a == b);
}
}
该程序输出的结果为 false
导致两个程序输出结果不同原因是 Integer 是装箱类型,Integer a
和 Integer b
指向的是堆中两块不同的内存。
我们再来看两个程序:
程序一:
public class Main {
public static void main(String[] args) {
Integer a = 1;
Integer b = 1;
System.out.println(a == b);
}
}
程序二:
public class Main {
public static void main(String[] args) {
Integer a = new Integer(1);
Integer b = new Integer(1);
System.out.println(a == b);
}
}
程序一的输出结果为:
true
程序二的输出结果为:
false
为什么会有这样的差异呢?
原因在于,当我们使用 Integer a = 1;
的方式声明一个变量时,Java 实际上会调用 Integer.valueOf()
这个方法。
我们来看下 Integer.valueOf()
这个方法的源代码:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
JDK 文档中说明:
- This method will always cache values in the range -128 to 127,
- inclusive, and may cache other values outside of this range.
也就是说由于 -128 ~ 127 这个区间的值非常常用, Java 为了减少申请内存的开销使用了 IntegerCache(Integer 常量池)将这些对象储存在常量池中,所以如果使用 Integer 声明的值在 -128 ~ 127 这个区间内的话,就会直接从常量池中取出并返回,于是我们看到程序一输出的结果为 true。
而使用Integer a = new Integer(1);
这种方式声明,则一定会在堆中开辟一块新的内存保存对象,所以程序二的输出结果为 false。
6:数组类型
数组类型是一个非常特殊的类型
数组类型由 JDK 专门使用虚拟机的一些指令来创建
数组的声明:
X[] x = new X[10]
X[] x = new X[]{...}
X[] x = {...}
数组的主要特性:
- 长度不可变
- 类型安全
- 只有一个 length 属性
- 可以使用 foreach 循环迭代