1:数据在计算机中是如何存储的

二进制,八进制,十六进制

二进制是计算技术中广泛采用的一种数制。所谓的二进制就是指由 01 两个数码表示的数。进位规则为: “逢二进一”。

计算机运算基础采用的就是二进制。原因在于:电子晶体管有两种基本状态,开和关,对应表示为 01

常用的进制还有 八进制以及十六进制,在电脑科学中,经常会用到 二进制与十六进制。

如何使用 Java 语言表示一个二进制,八进制与十六进制的数呢?

二进制表示:

  1. int a = 0b11

在数字前加上 0b 则表示一个二进制数,该数字的十进制表示为:3

八进制表示:

  1. int a = 011;

在数字前加上 0 则表示一个八进制数,该数字的十进制表示为:9

十六进制表示:

  1. int a = 0x11;

在数字前加上 0x 则表示一个十六进制数,该数字的十六进制表示为:17

同样的,我们也可以使用程序将十进制数字表示为二进制,八进制,十六进制的形式

  1. public class Main {
  2. public static void main(String[] args) {
  3. int a = 10;
  4. System.out.println(Integer.toBinaryString(a)); // 转换为二进制的字符串形式
  5. System.out.println(Integer.toHexString(a)); // 转换为十六进制的字符串形式
  6. System.out.println(Integer.toOctalString(a)); // 转换为八进制的字符串形式
  7. }
  8. }

字节

字节(Byte)是计算机信息技术用于计量存储容量的一种计量单位,一个字节存储 8 位无符号二进制数字
1 Byte = 8 bit

ASCII 码

在计算机中,所有的数据在存储和运算时都要使用二进制数来表示。

例如数字 32 的 十六进制表示为:20;二进制表示为:0010 0000

但是,除了数字以外,还有字母,空格等特殊符号需要被表示出来,而具体使用哪些二进制数来表示哪些符号,就要制定出一套编码规范为了大家相互通信时不造成混乱,那么大家必须使用这套编码规则。于是美国相关的标准化组织就推出了 ASCII 编码,既定了一套规范,到目前为止一共定义了 128 个字符。

MacLinux 操作系统的终端,我们可以输入命令:

  1. man ascii

来查看 ASCII 码表

3、Java数据类型 - 图1

比较常见的有:

  • 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_VALUEMAX_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 中只有两种数据类型,第一种是基本数据类型,第二种是引用类型;引用类型声明的是一个对象,保存在堆内存中。

装箱的目的:

  1. 装箱将一个基本数据类型包装成一个对象,一个类的对象就可以有很多可以调用的方法,便于我们进行操作
  2. 泛型不支持基本类型,例如:List<Integer> list = new ArrayList<>();
  3. 装箱类型可以指明为 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 的区别:

3、Java数据类型 - 图2

我们来看一个示例并进行说明

程序一:

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 aInteger 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 为了减少申请内存的开销使用了 IntegerCacheInteger 常量池)将这些对象储存在常量池中,所以如果使用 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 循环迭代