为啥 Java API 使用 int 而不是 short 或 byte?

Posted

技术标签:

【中文标题】为啥 Java API 使用 int 而不是 short 或 byte?【英文标题】:Why does the Java API use int instead of short or byte?为什么 Java API 使用 int 而不是 short 或 byte? 【发布时间】:2015-01-23 05:08:39 【问题描述】:

为什么Java API 使用int,而short 甚至byte 就足够了?

示例:Calendar 类中的 DAY_OF_WEEK 字段使用 int

如果差异太小,那为什么还存在这些数据类型(shortint)?

【问题讨论】:

【参考方案1】:

使用小于 CPU 总线大小的变量意味着需要更多的周期。例如更新内存中的单个字节时,64位CPU需要读取整个64位字,只修改改变的部分,然后写回结果。

此外,当变量存储在寄存器中时,使用较小的数据类型需要开销,因为要明确说明较小数据类型的行为。由于无论如何都使用了整个寄存器,因此对方法参数和局部变量使用较小的数据类型没有任何好处。

尽管如此,这些数据类型对于表示需要特定宽度的数据结构(例如网络数据包)或在大型数组中节省空间、牺牲速度可能很有用。

【讨论】:

非常有趣的东西! +1 我仍然认为这些是应该由 JVM 完成的优化。作为开发人员,我应该只关心我想要对类型执行的操作、它给出的语义值以及它提供的值的范围。 @WilliMentzel 即使在语义上使用Byte 表示月份中的某一天也不是必需的,因为对编译器来说意味着“请添加特殊行为以确保在 255 处溢出”,而不是“只是提醒一下,这个值很小”。最简单的类型并不总是最受限制的类型。我曾经和一位非常好的同事讨论过,如果你知道这些元素是不同的,那么使用SetList 是否更好,这是相同的;-)【参考方案2】:

(几乎)对byteshort的所有操作都会提升为int,比如不能写:

short x = 1;
short y = 2;

short z = x + y; //error

使用int 时,算术更简单直接,无需强制转换。

就空间而言,它非常差别不大。 byteshort 会使事情复杂化,我认为这种微优化不值得,因为我们谈论的是固定数量的变量。

byte 在您为嵌入式设备编程或处理文件/网络时是相关且有用的。这些原语也是有限的,如果将来计算可能超过它们的限制怎么办?尝试考虑扩展 Calendar 类,它可能会演变出更大的数字。

还要注意,在 64 位处理器中,局部变量将保存在寄存器中并且不会使用任何资源,因此使用 intshort 和其他原语根本不会有任何区别。此外,许多 Java 实现对齐变量*(和对象)。


*byteshort如果是局部变量、变量甚至是int占用相同的空间实例变量。为什么?因为在(大多数)计算机系统中,变量地址是对齐的,所以例如,如果你使用一个字节,你实际上会得到两个字节——一个用于变量本身,另一个用于填充.

另一方面,在数组中,byte 占用 1 个字节,short 占用 2 个字节,int 占用 4 个字节,因为在数组中只有开头和结尾必须对齐。如果您想使用这会有所不同,例如System.arraycopy(),那么您会真正注意到性能差异。

【讨论】:

有趣的事实:如果您对两个值都使用 final 修饰符,它将起作用。 :) @alexander 为什么? @elect 在这种情况下,编译器可以确定它们的总和是有效的short【参考方案3】:

实际上,会有一点优势。如果你有一个

class MyTimeAndDayOfWeek 
    byte dayOfWeek;
    byte hour;
    byte minute;
    byte second;

然后在典型的 JVM 上,它需要与包含单个 int 的类一样多的空间。内存消耗四舍五入到下一个 8 或 16 字节的倍数(IIRC,这是可配置的),因此真正节省的情况很少见。

如果相应的Calendar 方法返回byte,这个类会更容易使用。但是没有这样的Calendar 方法,只有get(int) 由于其他字段必须返回int。对较小类型的每个操作都会提升为 int,因此您需要大量转换。

很可能,您要么放弃并切换到int,要么编写类似的设置器

void setDayOfWeek(int dayOfWeek) 
    this.dayOfWeek = checkedCastToByte(dayOfWeek);

那么DAY_OF_WEEK 的类型无论如何都无所谓。

【讨论】:

我怀疑这些值是否会像这样打包,因为现代处理器上的非对齐内存访问会造成严重的性能损失。详情见***.com/questions/12491578/…【参考方案4】:

如果您使用整数常量存储在它们适合的最小类型中的哲学,那么 Java 将有一个严重的问题:每当程序员使用整数常量编写代码时,他们必须仔细注意他们的代码以检查是否常量的类型很重要,如果是,请在文档中查找类型和/或进行任何需要的类型转换。

既然我们已经概述了一个严重的问题,那么您希望通过这种理念获得什么好处?如果该更改的 only 运行时可观察效果是您通过反射查找常量时获得的类型,我不会感到惊讶。 (当然,无论是由懒惰/不知情的程序员引入的任何错误,都没有正确考虑常量的类型)

权衡利弊很容易:这是一种糟糕的哲学。

【讨论】:

【参考方案5】:

虚拟机的设计复杂度取决于它可以执行多少种操作。有四个指令的实现,比如“乘法”——一个用于 32 位整数、64 位整数、32 位浮点和 64 位浮点——比另外拥有更容易对上述内容,还有较小数值类型的版本。一个更有趣的设计问题是为什么应该有四种类型,而不是更少(使用 64 位整数执行所有整数计算和/或使用 64 位浮点值执行所有浮点计算)。使用 32 位整数的原因是 Java 有望在许多平台上运行,在这些平台上 32 位类型可以与 16 位或 8 位类型一样快,但对 64 位类型的操作会很明显慢点。即使在使用 16 位类型更快的平台上,使用 32 位数量的额外成本也会被 only 具有 32 位类型所提供的简单性所抵消。

至于对 32 位值执行浮点计算,优势就不太明显了。在某些平台上,可以通过将所有操作数转换为更高精度类型,将它们相加,然后将结果转换回 32 位浮点数进行存储,从而最快地执行像 float a=b+c+d; 这样的计算。在其他平台上,使用 32 位浮点值执行所有计算会更高效。 Java 的创建者决定应该要求所有平台都以相同的方式做事,并且他们应该支持 32 位浮点计算比更长的浮点计算更快的硬件平台,即使这严重降低了 PC 的速度。典型 PC 以及许多没有浮点单元的机器上的浮点数学精度和精度。请注意,顺便说一句,根据 b、c 和 d 的值,在计算像前面提到的 float a=b+c+d; 这样的表达式时使用更高精度的中间计算有时会产生比所有中间操作数所达到的结果要准确得多的结果以float 精度计算,但有时会产生一个不太准确的值。无论如何,Sun 决定一切都应该以同样的方式完成,他们选择使用最小精度的 float 值。

请注意,当大量数据类型一起存储在一个数组中时,较小数据类型的主要优势就会变得明显;即使拥有小于 64 位类型的单个变量没有任何优势,但拥有可以更紧凑地存储更小值的数组也是值得的;将局部变量设为byte 而不是long 可节省七个字节;具有 1,000,000 个数字的数组将每个数字保存为 byte 而不是 long 波 7,000,000 字节。由于每个数组类型只需要支持一些操作(最明显的是读取一项、存储一项、复制数组中的一系列项或将一系列项从一个数组复制到另一个数组),因此增加了更多的复杂性数组类型并不像拥有更多类型的可直接使用的离散数值那样复杂。

【讨论】:

【参考方案6】:

已经指出了一些原因。例如,"...(Almost) All operations on byte, short will promote these primitives to int".然而,下一个明显的问题是:为什么这些类型被提升为int

所以更深一层:答案可能只是与 Java 虚拟机指令集有关。正如Table in the Java Virtual Machine Specification中总结的那样,所有积分算术运算,如加法、除法等,仅适用于intlong类型, strong> 用于较小的类型。

(顺便说一句:较小的类型(byteshort)基本上只用于数组。像new byte[1000] 这样的数组会占用 1000 字节,而像 new int[1000] 这样的数组将占用 4000 字节)

现在,当然,可以说"...下一个明显的问题是:为什么这些说明只提供给int(和long)? "

上面提到的JVM Spec中提到了一个原因:

如果每个类型化指令都支持 Java 虚拟机的所有运行时数据类型,那么指令的数量将超过一个字节所能表示的数量

此外,Java 虚拟机可以被视为真实处理器的抽象。并且为较小的类型引入专用的Arithmetic Logic Unit 是不值得的:它需要额外的晶体管,但它仍然只能在一个时钟周期内执行一次加法。设计 JVM 时的主导架构是 32 位,正好适合 32 位 int。 (涉及 64 位 long 值的操作作为特例实现)。

(注意:考虑到可能的矢量化等,最后一段有点过于简单了,但应该给出基本概念,而不需要深入探讨处理器设计主题)


编辑:一个简短的附录,重点关注问题中的示例,但在更一般的意义上:人们也可以询问使用较小的类型存储 字段 是否有益。例如,有人可能认为可以通过将Calendar.DAY_OF_WEEK 存储为byte 来节省内存。但在这里,Java 类文件格式发挥作用:所有Fields in a Class File 至少占用一个“槽”,其大小为int(32 位)。 (“宽”字段doublelong 占用两个插槽)。因此,将字段明确声明为 shortbyte 也不会节省任何内存。

【讨论】:

我猜想为什么操作数被提升为 int 的逻辑也与C and C++中使用的基本原理有关 @Marco13 “因此明确地将字段声明为短或字节也不会节省任何内存。”真的吗?我不认为这是正确的。 @ACV 严格来说,实现可以选择存储更紧凑的形式,但是“虚拟”公开的格式(即由虚拟机)会将值视为至少具有大小的int。如果您对另一个实现有参考,我会更新答案并相应地插入链接。【参考方案7】:

因为与短裤相比,使用整数时算术运算更容易。假设常量确实是由short 值建模的。那么你必须以这种方式使用 API:

short month = Calendar.JUNE;
month = month + (short) 1; // is july

注意显式转换。在算术运算中使用短值时,它们会隐式提升为 int 值。 (在操作数堆栈上,short 甚至表示为整数。)这使用起来会非常麻烦,这就是为什么 int 值通常是常量的首选。

与此相比,存储效率的提升是最小的,因为只存在固定数量的此类常量。我们谈论的是 40 个常量。将他们的存储空间从int 更改为short 将保护您40 * 16 bit = 80 byte。如需进一步参考,请参阅this answer。

【讨论】:

以上是关于为啥 Java API 使用 int 而不是 short 或 byte?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 java.util.zip.CRC32.getValue() 返回一个 long 而不是 int?

为啥我们在枚举中写 Integer 而不是 int? [复制]

为啥 int[] a = new int[1] 而不是 int a?

Java 枚举 - 为啥使用 toString 而不是 name

为啥我应该在循环中使用 foreach 而不是 for (int i=0; i<length; i++) ?

为啥 C 和 C++ for 循环使用 int 而不是 unsigned int?