Java 短常量池与短原语

Sca*_*avs 1 java primitive pool reference

我的解决方案需要许多常量变量,因此在进一步的开发中,我可以简单地创建新的原语或引用现有数据,而不是创建新的原语,这排除了未来开发过程中可能出现的错误。

我读过java池常量变量,当创建新数据时,它与池进行比较,如果这样的对象存在,它返回对现有对象的引用,而不是创建新的对象。

虽然池化可能听起来是最好的方法,但就我而言,我需要许多变量,每个变量分配 2 个字节(对于原语)。但如果我创建Short我会丢失 2 个字节,因为引用将占用 4 个字节。

即使考虑到 Short 使用池化,仍然使用原语是否有意义。另外,从短到短的拆箱也需要一些资源(几乎接近于零,但仍然如此)。请注意,short 必须时常转换为原始 3 字节数组,因此另一个 + 表示原始。

public static final short USER = 10;
Run Code Online (Sandbox Code Playgroud)

代替

public static final Short USER = 10;
Run Code Online (Sandbox Code Playgroud)

bil*_*.cn 6

这里最重要的是,基元在时间和内存复杂度上都比对象包装器便宜得多。

仅当您必须在必须使用对象引用的上下文中使用这些原语时,池功能才变得相关(即它们必须被包装/“装箱”到其对象包装器中,谷歌自动装箱)。如果您可以始终将它们用作原始数字,那么这是最有效的方法。

细节:

Java 语言对待基元类型(boolean、byte、char、short、int、long、float、double)的方式与所有其他类型(引用类型)不同。原语可以直接存在于堆栈中,并且可以直接由 JVM 指令操作(每个原语都有一组指令)。数值常量通常直接嵌入到 JVM 指令中,这意味着执行这些指令不需要额外的内存读取。这种结构或多或少直接映射到所有硬件上的本机代码。

另一方面,引用类型不能存在于堆栈上,必须在堆上分配(这是 Java 语言的设计选择)。这意味着每次使用它们时,都必须添加指令来查找实例、读取元数据、调用方法或获取字段,然后才能对数据执行任何实际操作。

例如,假设您有以下功能

int add(int a, int b) { return a + b; }
Run Code Online (Sandbox Code Playgroud)

函数体(除了调用约定之外)很简单iadd,在大多数 CPU 上它转换为 1 条指令。

如果把ints改为Integerss,字节码就变成:

   0: aload_1
   1: invokevirtual #16                 // Method java/lang/Integer.intValue:()I
   4: aload_2
   5: invokevirtual #16                 // Method java/lang/Integer.intValue:()I
   8: iadd
   9: invokestatic  #22                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
Run Code Online (Sandbox Code Playgroud)

这转化为多个内部函数调用和数千条本机指令。

散记:

IIRC,Java 语言规范没有定义将使用多少字节来存储shortboolean等。JVM 实现可能会使用更多字节,因此它将所有内容与 CPU 的字长对齐。纯粹为了提高效率而将布尔值存储为字节或 32 位 int 的情况并不罕见。int然而,它确实说过,对比所有类型都短的类型进行操作的结果是int

所有这些方法short并不能真正为您节省任何内存,除非您有非常大的short数组(必须在内存中没有间隙的情况下打包)。

如果您真的非常想使用 s 池Short(包装器),最有效的实现是大小为 65536 的数组。在这里,您可以交换 4k/8k 内存以实现非常高效的查找。