Java 短常量池与短原语

Java Short constant pool versus short primitives

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

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

虽然合并可能听起来是最好的方法,但在我的例子中,我需要许多 short 变量,每个变量分配 2 个字节(对于原始变量)。但是如果我创建 Short 我会丢失 2 个字节,因为引用需要 4 个字节。

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

public static final short USER = 10;

而不是

public static final Short USER = 10;

这里最重要的是,在时间和内存复杂性方面,原语比对象包装器便宜得多。

仅当您必须在必须使用对象引用的上下文中使用这些基元时,池功能才变得相关(即它们必须被包装/"boxed" 到它们的对象包装器中,google自动装箱)。如果你能一直把它们当作原始数来使用,那是最有效的方法。

详情:

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

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

例如,假设您有函数

int add(int a, int b) { return a + b; }

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

如果把int改成Integers,字节码就变成:

   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;

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

随机笔记:

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

所有这些意味着 short 不会真正为您节省任何内存,除非您有非常大的 short 数组(必须在内存中没有间隙地打包)。

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

我不太清楚你为什么需要 Short。无论如何,作为一个经验法则,请始终记住 与它们的包装器 相比,基元总是更快、更便宜。它们不是对象,因此许多 JIT 优化可以产生非常快的代码。此外,Wrappers 不包含所有可能值的缓存 - 例如 Integer 具有(默认情况下)-128 到 127 的缓存。您可以扩展它,但这不是重点。

如果您需要将 shorts 放入集合中,那么使用缓存可能是有意义的(包装器已经有 -128 到 127 之间的缓存)。然而,如果您想推送数百万个数字,那么不使用缓存更有意义。