基本数据类型 & 包装类

Author: Corissa 👵

Date: Dec.29 2023

重点需要掌握🏁:

理解 自动装箱/拆箱

包装类的缓存机制

为什么需要基本数据类型

1 基本数据类型和包装类的基本回顾

  1. 基本数据类型 8 种

    byte (1 byte), char (2 byte), short (2 byte), int (4 byte), long (8 byte), float (4 byte), double (8 byte), boolean

    对于 boolean,官方文档未明确定义,依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素。

  2. 8 种基本数据类型都有对应的包装类

    Byte, Character, Short, Integer, Long, Float, Double, Boolean

  3. 基本数据类型 vs 包装类型

    • 用途

      • 包装类型可以用于泛型,而基本类型不可以
      • 基本数据类型的使用场景比较局限,容器存储时也不能存储基本数据类型。
    • 存储方式

      • 基本数据类型的局部变量存储在 Java 虚拟机栈的局部变量表中,成员变量(未被statics 修饰)存放在堆中。
      • 包装类型的实例存放在堆中。
    • 占用空间

      • 基本数据类型占用空间远小于包装类型,因为对象在内存中存储不仅包含实例数据,还包含对象头和对其填充,以下是《深入理解JAVA虚拟机的描述》:

      在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

      HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,官方称它为”Mark Word”。

      对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据信息并不一定要经过对象本身,这点将在2.3.3节讨论。另外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中却无法确定数组的大小。

      接下来的实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。

      第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。

    • 比较方式

      • 对于基本数据类型来说 “==” 比较的是值
      • 对于包装类型来说 “==” 比较的是对象内存地址,equals() 比较的是值

    2 包装类型的缓存机制

Java 基本数据类型的包装类型大部分都用到了缓存机制来提升性能。

Java 官方对 IntegerCache 描述如下:

Cache to support the object identity semantics of autoboxing for values between -128 and 127 (inclusive) as required by JLS.

以 Integer 为例,在自动装箱时,如果值大小在 -128~127 之间则从使用缓存中的对象:

1
2
3
4
5
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

比如说:

1
2
3
Integer i1 = 20;
Integer i2 = new Integer(20);
System.out.println(i1==i2);

这里 i1 是自动装箱,会直接使用缓存中的20,i2 是创建新的对象,因此内存地址不同,打印 false

对于 Byte,Short,Integer,Long 这 4 种包装类,JVM 默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False

对于包装类 Float,Double 并没有实现缓存机制。

3 自动装箱(boxing) & 自动拆箱(unboxing)

  • boxing: 将基本数据类型用对应的引用包装起来,通过调用包装类的 valueOf()方法实现
  • unboxing: 将包装类型转换为基本数据类型, 通过调用包装类的xxxValue() 方法如 intValue()实现

原则上,尽量避免无意义的拆箱、装箱行为,会影响系统性能

4 其他

  1. 包装类的 value 也被声明为不可变类型:private final int value;

  2. 基本数据类型线程安全问题

    基本数据类型的变量,显然要使用并发相关手段,才能保证线程安全。

    比如可以使用类似 AtomicIntegerAtomicLong 这样的线程安全类。特别的是,部分比较宽的数据类型,比如 float、double,可能甚至不能保证更新操作的原子性,可能出现程序读取到只更新了一半数据位的数值