为什么Java API使用int
,何时short
甚至byte
足够使用?
示例:DAY_OF_WEEK
类中的字段Calendar
使用int
。
如果差异太小,那为什么这些数据类型(short
,int
)根本存在呢?
为什么Java API使用int
,何时short
甚至byte
足够使用?
示例:DAY_OF_WEEK
类中的字段Calendar
使用int
。
如果差异太小,那为什么这些数据类型(short
,int
)根本存在呢?
Answers:
已经指出了一些原因。例如,“ ...(几乎)对byte,short的所有操作将把这些原语提升为int”这一事实。但是,下一个显而易见的问题是:为什么将这些类型提升为int
?
因此,要更深入一点:答案可能只是与Java虚拟机指令集有关。如Java虚拟机规范的表中所概述,所有积分算术运算(例如加法,除法和其他运算)仅适用于type int
和type long
,而不适用于较小的类型。
(顺便说一句,较小的类型(byte
和short
)基本仅用于数组。like这样的数组new byte[1000]
将占用1000个字节,而like这样的数组new int[1000]
将占用4000个字节)
现在,当然可以说:“ ...下一个明显的问题是:为什么这些说明仅用于int
(和long
)?” 。
上面提到的JVM Spec中提到了一个原因:
如果每个类型的指令都支持Java虚拟机的所有运行时数据类型,那么指令的数量将超过一个字节表示的数量。
另外,可以将Java虚拟机视为真实处理器的抽象。为较小的型号引入专用的算术逻辑单元将是不值得的:它需要额外的晶体管,但仍然只能在一个时钟周期内执行一次加法运算。设计JVM时,主要的体系结构是32位,正好是32位int
。(在long
特殊情况下,将执行涉及64位值的操作)。
(注意:考虑到可能的向量化等,最后一段有点过分简化,但应该给出基本思想,而不必深入探讨处理器设计主题)
编辑:一个简短的附录,重点放在问题的示例上,但是从更一般的意义上讲:还可以问一下使用较小类型存储字段是否有益。例如,人们可能认为可以通过存储Calendar.DAY_OF_WEEK
为来节省内存byte
。但是在这里,Java类文件格式开始起作用:类文件中的所有字段至少占据一个“槽”,其大小为1 int
(32位)。(“宽”字段double
和和long
占据两个位置)。因此,显式地将字段声明为short
or byte
不会保存任何内存。
int
。如果您有其他实现的参考,我将更新答案并相应地插入链接。
(几乎)所有操作byte
,short
将促进他们int
,例如,你不能写:
short x = 1;
short y = 2;
short z = x + y; //error
使用时int
,算术更简单明了,无需强制转换。
在空间方面,它使一个非常小的差异。byte
并且short
会使事情复杂化,因为我们谈论的是固定数量的变量,所以我认为这种微优化不值得。
byte
在为嵌入式设备编程或处理文件/网络时,它是相关且有用的。这些原语也受到限制,如果将来计算可能超出其限制怎么办?尝试考虑Calendar
可能会演变出更大数字的类扩展。
还要注意的是,在64位处理器,当地人将因此使用保存在寄存器中,不会使用任何资源int
,short
和其他原语不会让所有任何区别。此外,许多Java实现将变量*(和对象)对齐。
* byte
并short
占据相同的空间,就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
。请参阅此答案以获取更多参考。
如果您使用将整数常量存储在它们适合的最小类型中的哲学,那么Java将会遇到一个严重的问题:每当程序员使用整数常量编写代码时,他们都必须特别注意自己的代码,以检查其类型是否正确。常量很重要,如果需要,可以在文档中查找类型和/或进行任何需要的类型转换。
因此,既然我们已经概述了一个严重的问题,那么您希望通过这种理念获得什么好处?如果该更改的唯一可观察到的运行时效果是通过反射查找常量时得到的类型,那我就不会感到惊讶。(当然,由于懒惰/无意识的程序员引入的任何错误都没有正确考虑常量的类型)
权衡利弊非常容易:这是一个糟糕的哲学。
虚拟机的设计复杂度取决于它可以执行多少种操作。此外,具有“乘法”之类的四种指令实现要容易得多,每个实现分别用于32位整数,64位整数,32位浮点和64位浮点。除上述以外,较小数字类型的版本也是如此。一个更有趣的设计问题是为什么应该有四种类型,而不是更少(使用64位整数执行所有整数计算和/或使用64位浮点值执行所有浮点计算)。使用32位整数的原因是,Java有望在许多平台上运行,在这些平台上,对32位类型的作用可能与对16位或8位类型的作用一样快,但是对64位类型的操作将很明显慢点。仅具有32位类型。
至于对32位值执行浮点计算,其优势还不太清楚。在某些平台上,诸如float a=b+c+d;
通过将所有操作数转换为高精度类型,将它们相加,然后将结果转换回32位浮点数进行存储,可以最快速地执行操作。在其他平台上,使用32位浮点值执行所有计算会更高效。Java的创建者决定应该要求所有平台都以相同的方式执行操作,并且他们应该偏向于硬件平台,因为32位浮点计算的速度要比较长的硬件平台快,即使这会严重降低PC的速度和典型PC上以及许多不带浮点单元的机器上浮点数学的精度和精确度。顺便说一句,顺便说一句,根据b,c和d的值,在计算上述表达式时使用高精度中间计算float a=b+c+d;
有时所产生的结果要比所有以float
精确度计算的所有中间操作数所获得的结果要精确得多,但是有时所产生的值精度会稍差一点。无论如何,Sun决定一切都应以相同的方式完成,他们选择使用最小精度float
值。
请注意,较小的数据类型的主要优点在将大量数据存储在一个数组中时变得明显。即使使用小于64位类型的单个变量没有任何优势,也值得拥有可以更紧凑地存储较小值的数组;使局部变量为a byte
而不是long
保存七个字节;包含1,000,000个数字的数组,则每个数字都以a byte
而不是along
波7,000,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
无论如何,类型都不重要。