为什么 JavaAPI 使用 int 而不是 short 或 byte?

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

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

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

14071 次浏览

(几乎) 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(),那么这将产生不同,然后您将真正注意到性能差异。

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

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

注意显式的强制转换。当在算术运算中使用短值时,短值被隐式提升为 int值。(在操作数堆栈上,short 甚至表示为 int。)这将是相当繁琐的使用,这就是为什么 int值通常是常量的首选。

与此相比,存储效率的增益是最小的,因为这种常数只存在一个固定的数目。我们讨论的是40个常量。将它们的存储从 int改为 short可以保证 40 * 16 bit = 80 byte的安全。请参阅 这个答案以获得进一步的参考。

一些原因已经被指出。例如,事实上 所有对 byte 的操作,short 都会将这些原语提升为 int。然而,下一个显而易见的问题是: 为什么是否将这些类型提升为 int

所以更深一层的答案可能与 Java 虚拟指令集有关。正如在 Java 虚拟机规范中的表中总结的那样,所有整数算术运算,如加法、除法等,只适用于 int类型和 long类型,而 没有则适用于较小的类型。

(另外: 较小的类型(byteshort)基本上只适用于 数组。像 new byte[1000]这样的 数组将占用1000字节,而像 new int[1000]这样的数组将占用4000字节)

现在,当然,可以说 “ ... 下一个显而易见的问题将是: 为什么这些指令只提供给 ABC0(和 long) ?”

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

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

此外,Java 虚拟机可以被看作是实际处理器的抽象。为较小型号引入专用的 算术逻辑单元并不值得付出这样的努力: 它需要额外的晶体管,但它仍然只能在一个时钟周期内执行一次附加操作。JVM 设计时的主导架构是32位的,刚好适合32位的 int。(涉及64位 long值的操作作为特殊情况实现)。

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


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

如果你用整数常量存储在最小的类型中的哲学,那么 Java 将会有一个严重的问题: 无论何时程序员使用整数常量编写代码,他们必须仔细注意他们的代码来检查常量的类型是否重要,如果是这样,在文档中查找类型和/或做任何需要的类型转换。

既然我们已经概述了一个严肃的问题,那么你希望通过这种哲学来获得什么样的好处呢?如果 只有运行时观察到的更改效果是通过反射查找常量时得到的类型,我不会感到惊讶。(当然,还有那些懒惰/不知情的程序员没有正确计算常量类型而引入的任何错误)

权衡利弊非常简单: 这是一种糟糕的哲学。

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

至于对32位值执行浮点计算,其优点就不那么明显了。在一些平台上,像 float a=b+c+d;这样的计算可以通过将所有操作数转换为更高精度的类型、添加它们,然后将结果转换回32位浮点数来存储,从而最快地执行。在其他平台上,使用32位浮点值执行所有计算会更有效率。Java 的创造者决定所有的平台都应该被要求以相同的方式做事情,并且他们应该支持那些32位浮点运算比较长的计算速度更快的硬件平台,即使这严重地降低了一台典型 PC 上浮点运算的速度和精度,以及在许多没有浮点运算单元的机器上。顺便说一下,根据 b、 c 和 d 的值,在计算诸如上述 float a=b+c+d;这样的表达式时,使用更高精度的中间计算有时会产生比在 float精度下计算的所有中间操作数更精确的结果,但有时会产生一个稍微不那么精确的值。在任何情况下,Sun 决定所有事情都应该按照相同的方式进行,并且他们选择使用最小精度的 float值。

请注意,小型数据类型的主要优势在大量数据存储在一个数组中时变得明显; 即使没有优势的类型小于64位的单个变量,有数组可以存储更小的值更紧凑; 有一个局部变量是 byte而不是 long可以节省7个字节; 有一个1000,000个数字的数组保存每个数字作为 byte而不是 long波7000,000字节。由于每个数组类型只需要支持几个操作(最明显的是读取一个项目,存储一个项目,在数组中复制一系列项目,或者将一系列项目从一个数组复制到另一个数组) ,因此拥有更多数组类型的额外复杂性并不像拥有更多类型的可直接使用的离散数值的复杂性那样严重。

事实上,有一个小优势。如果你有一个

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的类型就无关紧要了。

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

此外,当变量存储在寄存器中时,使用较小的数据类型需要开销,因为要显式考虑较小数据类型的行为。由于总是使用整个寄存器,因此对方法参数和局部变量使用较小的数据类型不会有任何收获。

然而,这些数据类型可能有助于表示需要特定宽度的数据结构,如网络数据包,或节省大型数组中的空间,从而牺牲速度。