为什么Java API使用int而不是short或byte?
为什么Java API使用int
如果short
或甚至byte
就足够了?
示例: Calendar
类中的DAY_OF_WEEK
字段使用int
。
如果差异太小,为什么这些数据types(short,int)完全存在?
一些原因已经被指出。 例如, “…(几乎)所有对字节的操作,短的事实将这些原语提升为int” 。 然而,明显的下一个问题是: 为什么这些types被提升为int
?
所以要深入一层:答案可能只是与Java虚拟机指令集相关。 正如Java虚拟机规范中的表格所总结的那样, 所有的积分算术运算,比如加法,除法等等,只能用于int
types和long
types,而不能用于较小的types。
(另外:较小的types( byte
和short
)基本上只用于数组,像new byte[1000]
这样的数组需要1000字节,像new int[1000]
这样的数组需要4000字节)
现在,当然可以这样说: “……明显的下一个问题是: 为什么这些指令只提供int
( long
)? 。
在上面提到的JVM规范中提到了一个原因:
如果每种types的指令都支持所有的Java虚拟机的运行时数据types,那么将会有比在一个字节中表示更多的指令
另外,Java虚拟机可以被认为是真实处理器的抽象。 引入专用的算术逻辑单元对于较小的types是不值得的:它将需要额外的晶体pipe,但它仍然只能在一个时钟周期内执行一次加法。 JVMdevise时的主要架构是32位,恰到好处的是32位int
。 (涉及64位long
值的操作作为特例实现)。
(注意:考虑到可能的vector化等等,最后一段有些过于简化了,但是应该给出基本思想,而不要过分深入处理器devise主题)
编辑:一个简短的附录,侧重于从这个问题的例子,但在一个更普遍的意义上:人们也可以问,使用较小的types存储字段是否是有益的。 例如,有人可能认为可以通过将Calendar.DAY_OF_WEEK
存储为byte
来保存内存。 但在这里,Java类文件格式发挥作用: 类文件中的所有字段至less占用一个“槽”,其大小为一个int
(32位)。 (“宽”字段, double
和long
,占用两个插槽)。 所以显式声明一个字段为short
或者byte
也不会保存任何内存。
(几乎)对byte
, short
所有操作将把它们提升为int
,例如,你不能写:
short x = 1; short y = 2; short z = x + y; //error
当使用int
,算术更简单直接,不需要投射。
在空间方面,这只是一个很小的差别。 byte
和short
会使事情复杂化,我不认为这个微型优化是值得的,因为我们正在谈论一个固定数量的variables。
当您为embedded式设备编程或处理文件/networking时, byte
是相关且有用的。 而且这些原始数据是有限的,如果计算将来可能超出其极限呢? 尝试考虑可能演变大数字的Calendar
类的扩展。
另外请注意,在64位处理器中,本地人将被保存在寄存器中,不会使用任何资源,所以使用int
, short
和其他原语根本不会有任何区别。 而且,许多Java实现alignmentvariables* (和对象)。
* byte
和short
如果是局部variables, 类variables甚至实例variables,则占用与int
相同的空间。 为什么? 因为在(大多数)计算机系统中,variables地址是alignment的 ,所以例如,如果你使用单个字节,你实际上最终会得到两个字节 – 一个用于variables本身,另一个用于填充。
另一方面,在数组中, byte
占用1个字节, short
占用2个字节, int
占用4个字节,因为在数组中只有开始和结束必须alignment。 如果你想使用System.arraycopy()
,这将会有所作为,那么你会注意到性能的差异。
因为与短裤相比,使用整数时算术运算更容易。 假定这些常数确实是由short
值build模的。 那么你将不得不以这种方式使用API:
short month = Calendar.JUNE; month = month + (short) 1; // is july
注意显式的投射。 当在算术运算中使用short值时,隐式地将其提升为int
值。 (在操作数堆栈中,短裤甚至表示为整数)。这将会非常麻烦,这就是为什么int
值常常是常量的首选。
与此相比,存储效率的增益是最小的,因为只存在固定数量的这种常数。 我们正在谈论40个常量。 将它们的存储从int
更改为short
可以保证你的40 * 16 bit = 80 byte
。 看到这个答案进一步参考。
如果你使用哲学,其中整型常量以适合的最小types存储,那么Java将有一个严重的问题:每当程序员使用整型常量编写代码时,他们必须仔细注意他们的代码,以检查types常量很重要,如果是这样的话,查看文档中的types和/或做任何需要的types转换。
那么现在我们已经概述了一个严重的问题,那么您希望通过这个理念获得什么样的好处? 如果这种改变的唯一的运行时可观察的效果是通过reflection查看常量时得到的types,那么我不会感到惊讶。 (当然,无论懒惰/不知情的程序员引入了什么错误都不能正确地解释常量的types)
权衡利弊很容易:这是一个不好的理念。
虚拟机的devise复杂性是它可以执行多less种操作的函数。 有一个像“乘”的指令的四个实现比32位整数,64位整数,32位浮点和64位浮点更容易,到上面,更小的数字types的版本也是如此。 一个更有趣的devise问题是为什么应该有四种types,而不是更less(用64位整数执行所有整数计算和/或用64位浮点值执行所有浮点计算)。 使用32位整数的原因是,Java预计可以在许多平台上运行,其中32位types可以像16位或8位types一样快速地运行,但是对于64位types的操作将是显着的比较慢。 即使在16位types可以更快处理的平台上,使用32位数据的额外成本也会被只有 32位数据types所提供的简单性所抵消。
至于在32位值上执行浮点计算,其优点有点不太清楚。 有一些平台,像float a=b+c+d;
可以通过将所有操作数转换为更高精度的types,将它们相加,然后将结果转换回32位浮点数进行存储来最快速地执行。 在其他平台上,使用32位浮点值执行所有计算会更高效。 Java的创造者决定,所有的平台都应该被要求以同样的方式来做,而且他们应该偏好那些32位浮点计算速度比较长的计算速度更快的硬件平台,尽pipe这样会严重降低PC的速度以及典型PC上的浮点运算精度,以及许多没有浮点单元的机器。 注意,顺便说一下,根据b,c和d的值,在计算像上面提到的float a=b+c+d;
这样的expression式时,使用更高精度的中间计算float a=b+c+d;
有时会得到比所有中间操作数在float
精度下计算得到的结果明显更准确的结果,但是有时候会产生一个稍微不准确的值。 无论如何,Sun决定应该以同样的方式完成所有的事情,他们select使用最小精度float
值。
请注意,较小数据types的主要优点在将大量数据一起存储在一个数组中时变得明显; 即使对于小于64位的types的单个variables没有好处,值得拥有能够更小型地存储更小值的数组; 有一个局部variables是一个byte
而不是一个long
保存七个字节; 拥有一个数字为1,000,000的数字将每个数字保存为一个byte
而不是长波700万字节。 由于每个数组types只需要支持一些操作(最显着的是读取一个项目,存储一个项目,复制数组中的一系列项目,或者将一个项目的范围从一个数组复制到另一个),增加了更多的复杂性数组types不像具有更多types的可直接使用的离散数值那样复杂。
事实上,这会有一个小小的优势。 如果你有一个
class MyTimeAndDayOfWeek { byte dayOfWeek; byte hour; byte minute; byte second; }
那么在一个典型的JVM上,它需要和包含一个int
的类一样多的空间。 内存消耗四舍五入到8或16字节的下一个倍数(IIRC,这是可configuration的),所以真正节省的情况是非常罕见的。
如果相应的Calendar
方法返回一个byte
这个类会稍微容易一些。 但是没有这样的Calendar
方法,只有get(int)
必须返回一个int
因为其他字段。 对较小types的每个操作提升为int
,所以需要大量的投射。
最有可能的是,你要么放弃和切换到一个int
或写入setters像
void setDayOfWeek(int dayOfWeek) { this.dayOfWeek = checkedCastToByte(dayOfWeek); }
那么DAY_OF_WEEK
的types无关紧要。