为什么Java具有不同大小数字的原语?


20

在Java中有原始类型byteshortintlong和同样的事情floatdouble。为什么必须要由一个人来设置原始值应使用多少个字节?是否只能根据传入的数字大小来动态确定大小?

我能想到的原因有两个:

  1. 动态设置数据大小将意味着它也需要能够动态更改。这可能会导致性能问题吗?
  2. 也许程序员不希望某人能够使用比特定大小更大的数字,并且这使他们受到限制。

我仍然认为通过使用单个intfloat类型进行简单操作可能会获得很多收益,是否有特定的原因导致Java决定不走这条路?


4
对于拒绝投票的人,我要补充一点,这个问题与编译器研究人员希望回答的问题有关
rwong

因此,如果您添加数字,您认为应该动态更改类型?我什至要更改类型吗?如果数字初始化为intUnknown alpha = a + b; 你得到那将对编译器有点困难。为什么这特定于Java?
狗仔队

@Paparazzi现有的编程语言和执行环境(编译器,解释器等)将根据实际值的大小(例如加法运算的结果)存储动态宽度整数。结果是:要在CPU上执行的代码变得更加复杂;该整数的大小变为动态;从内存中读取动态宽度整数可能需要不止一次;在其字段/元素内部包含动态宽度整数的结构(对象)和数组也可能具有动态大小。
rwong

1
@我不明白。只需以您喜欢的任何格式发送数字:十进制,二进制等。序列化是一个完全正交的问题。
gardenhead

1
@gardenhead这是正交的,是的,但是...考虑一下您要在用Java编写的服务器和用C编写的客户端之间进行通信的情况。当然,可以使用专用的基础结构来解决。例如,有些东西像developers.google.com/protocol-buffers。但这对于在网络上传输整数的小坚果来说是一个大锤。(我知道,这不是一个有力的论据,但也许是一个需要考虑的问题-讨论详细信息超出了评论的范围)。
Marco13 '16

Answers:


16

就像语言设计的许多方面一样,它涉及到了优雅与性能的权衡(更不用说早期语言的一些历史影响了)。

备择方案

当然,有可能(而且非常简单)制作一种仅具有一种自然数类型的编程语言nat。您认为,几乎所有用于学术研究的编程语言(例如PCF,System F)都具有这种单一数字类型,这是一种更为优雅的解决方案。但是实践中的语言设计不仅仅是优雅。我们还必须考虑性能(对性能的考虑程度取决于语言的预期应用)。性能包括时间和空间限制。

空间限制

让程序员预先选择字节数可以节省内存受限程序的空间。如果所有数字都小于256,则可以使用bytes的8倍long,或者将保存的存储空间用于更复杂的对象。标准Java应用程序开发人员不必担心这些约束,但是确实存在。

效率

即使我们忽略空间,我们仍然受到CPU的限制,CPU仅具有以固定数量的字节(在64位架构上为8字节)操作的指令。这意味着,即使能够提供单个8字节long类型的语言,也能够通过将算术运算直接映射到单个基础CPU指令,从而使该语言的实现比具有无界自然数类型的方法更加简单。如果允许程序员使用任意大的数字,则必须将单个算术运算映射到一系列复杂的机器指令,这会使程序变慢。这是您提出的要点(1)。

浮点类型

到目前为止,讨论仅涉及整数。浮点类型是一个复杂的野兽,具有极其微妙的语义和边缘情况。因此,即使我们可以很容易地更换intlongshort,并byte用一个单一的nat类型,目前还不清楚是什么浮点数类型甚至。显然,它们不是实数,因为在编程语言中不能存在实数。它们也不是很合理的数字(尽管如果需要,可以直接创建一个合理的类型)。基本上,IEEE决定了一种近似实数的方法,从那时起,所有语言(和程序员)就一直受困于它们。

最后:

也许程序员不希望某人能够使用比特定大小更大的数字,并且这使他们受到限制。

这不是正当理由。首先,我无法想到类型可以自然地编码数字范围的任何情况,更不用说程序员天生地希望程序员强制执行的范围恰好与任何原始类型的大小相对应的可能性。


2
我们拥有花车这一事实的真正关键在于,我们为它们配备了专用硬件
jk。

在类型中对数字范围进行编码绝对也确实会在从属类型语言中发生,并且在较小程度上会出现其他语言,例如枚举
jk。

3
枚举不等于整数。枚举只是使用和类型的一种方式。一些语言将枚举透明地编码为整数的事实是语言缺陷,而不是可利用的功能。
gardenhead

1
我对Ada不熟悉。我可以将整数限制为任何类型type my_type = int (7, 2343)吗?
gardenhead

1
是的 语法为:type my_type的范围是7..2343
Devsman's

9

原因很简单:效率。有多种方式。

  1. 本机数据类型:语言的数据类型与硬件的基础数据类型越接近,则该语言的效率就越高。(从某种意义上说,您的程序不一定一定是有效的,但在某种意义上,如果您真的知道自己在做什么,则可以编写与硬件可以运行的效率差不多的代码。)提供的数据类型Java对应于其中最流行的硬件的字节,单词,双字和quadwords。这是最有效的方法。

  2. 32位系统上不必要的开销:如果已决定将所有内容映射到固定大小的64位长,那么这将对需要大量时钟周期才能执行64位操作的32位体系结构造成巨大的损失。位操作要比32位操作大。

  3. 内存浪费:那里有很多硬件对内存对齐不太挑剔(例如Intel x86和x64体系结构就是这样),因此该硬件上的100字节数组只能占用100字节内存。但是,如果您不再有字节,而必须使用long代替,则同一数组将占用更多的内存。字节数组很常见。

  4. 计算数字大小:根据传入数字的大小动态确定整数大小的想法过于简单;没有“传递”数字的单点;必须在运行时对可能需要更大结果的每个单个操作进行一个大数的计算:每次增加一个数字,每次添加两个数字,每次相乘两个数字等

  5. 对不同大小的数字进行的操作:随后,在内存中浮动可能具有不同大小的数字将使所有操作变得复杂:即使只是简单地比较两个数字,运行时也必须首先检查要比较的两个数字是否相同大小,如果没有,则调整较小的大小以匹配较大的大小。

  6. 需要特定操作数大小的运算:某些按位运算依赖于具有特定大小的整数。由于没有预先确定的特定大小,因此必须模拟这些操作。

  7. 多态性的开销:在运行时更改数字的大小实际上意味着它必须是多态的。反过来,这意味着它不能是在堆栈上分配的固定大小的原语,而必须是在堆上分配的对象。那是非常低效的。(重新阅读上面的#1。)


6

为了避免重复其他答案中已讨论的观点,我将尝试概述多种观点。

从语言设计的角度

  • 当然,可以设计和实现一种编程语言及其执行环境,以自动适应不适合机器宽度的整数运算的结果。
  • 语言设计者可以选择是否将这种动态宽度整数设置为该语言的默认整数类型。
  • 但是,语言设计者必须考虑以下缺点:
    • CPU将不得不执行更多的代码,这需要更多的时间。但是,可以针对整数适合单个机器字的最常见情况进行优化。请参见标记的指针表示形式
    • 该整数的大小变为动态。
    • 从内存中读取动态宽度整数可能需要超过一趟。
    • 在其字段/元素内部包含动态宽度整数的结构(对象)和数组也将具有动态的总(占用)大小。

历史原因

Wikipedia上有关Java历史的文章已经对此进行了讨论,并且Marco13的答案对此也进行了简要讨论。

我要指出的是:

  • 语言设计师必须在美学和务实的思维方式之间进行折衷。审美观念希望设计一种不易出现众所周知的问题(例如整数溢出)的语言。务实的心态提醒设计者,编程语言必须足够好,才能实现有用的软件应用程序,并与以不同语言实现的其他软件部分互操作。
  • 打算从较旧的编程语言中夺取市场份额的编程语言可能更倾向于实用。一个可能的结果是,他们更愿意从那些较旧的语言中合并或借鉴现有的编程结构和样式。

效率原因

效率何时重要?

  • 当您打算宣传适合大型应用程序开发的编程语言时。
  • 当您需要处理数以亿计的小物件时,效率的每一点都会加起来。
  • 当您需要与另一种编程语言竞争时,您的语言需要表现出色-它不一定是最好的,但是它无疑有助于保持接近最佳性能。

存储效率(在内存中或在磁盘上)

  • 计算机内存曾经是一种稀缺资源。在过去的日子里,计算机可以处理的应用程序数据的大小受到计算机内存量的限制,尽管可以使用聪明的编程来解决(这将花费更多的实现成本)。

执行效率(在CPU内,或在CPU与内存之间)

  • 园丁的回答中已经讨论过了。
  • 如果程序需要处理非常大的连续存储的小数组,则内存中表示的效率会直接影响其执行性能,因为大量数据会导致CPU和内存之间的吞吐量成为瓶颈。在这种情况下,更密集地打包数据意味着单个高速缓存行提取可以检索更多数据。
  • 但是,如果没有连续存储或处理数据,则此推理不适用。

需要编程语言为小整数提供抽象,即使仅限于特定上下文

  • 这些需求经常出现在软件库的开发中,包括该语言自己的标准库。以下是几种此类情况。

互通性

  • 通常,高级编程语言需要与操作系统或使用其他低级语言编写的软件(库)进行交互。这些低级语言通常使用“结构”进行通信,“结构”是由不同类型的字段组成的记录的内存布局的严格规范。
  • 例如,高级语言可能需要指定某个外部函数接受char大小为256 的数组。(示例。)
  • 操作系统和文件系统使用的某些抽象要求使用字节流。
  • 一些编程语言选择提供实用程序功能(例如BitConverter),以帮助将窄整数打包和拆包为比特流和字节流。
  • 在这些情况下,较窄的整数类型不必是语言内置的原始类型。而是可以将它们提供为库类型。

字符串处理

  • 有些应用程序的主要设计目的是操纵字符串。因此,字符串处理的效率对这些类型的应用程序很重要。

文件格式处理

  • 许多文件格式都是采用类似C的思维方式设计的。因此,普遍使用窄宽度场。

需求,软件质量和程序员的责任

  • 对于许多类型的应用程序,自动扩展整数实际上并不是理想的功能。饱和度和回绕率(模量)都不是。
  • 程序员对软件的各个关键点(例如API级别)的最大允许值的明确指定,将使许多类型的应用程序受益。

请考虑以下情形。

  • 软件API接受JSON请求。该请求包含一系列子请求。可以使用Deflate算法压缩整个JSON请求。
  • 恶意用户会创建一个包含十亿个子请求的JSON请求。所有子请求均相同;恶意用户打算使系统消耗一些CPU周期来完成无用的工作。由于压缩,这些相同的子请求被压缩到很小的总大小。
  • 显然,对数据压缩大小的预定义限制是不够的。相反,API需要对可包含在其中的子请求的数量施加预定义的限制,和/或对数据的压缩大小施加预定义的限制。

通常,必须为此目的设计出可以安全地扩展多个数量级的软件,并且其复杂性不断增加。即使消除了整数溢出问题,它也不会自动出现。这完全回答了语言设计的观点:通常,当发生意外的整数溢出(通过抛出错误或异常)时拒绝执行工作的软件比自动遵循天文学上的大型运算的软件更好。

这意味着OP的观点,

为什么必须要由一个人来设置原始值应使用多少个字节?

是不正确的。应该允许程序员有时甚至是要求程序员在软件的关键部分指定整数值可以采用的最大大小。正如Gardenhead的答案所指出的那样,原始类型施加的自然限制对此无济于事。该语言必须为程序员提供声明幅度并强制执行此类限制的方法。


2

全部来自硬件。

字节是大多数硬件上内存的最小可寻址单元。

您刚才提到的每种类型都是从多个字节构建的。

一个字节是8位。这样一来,您可以表示8个布尔值,但不能一次只查询一个布尔值。您的地址为1,全部为8。

它曾经很简单,但是后来我们从8位总线变成了16位,32位和现在的64位总线。

这意味着尽管我们仍然可以在字节级别进行寻址,但如果不获取其相邻字节就无法再从内存中检索单个字节。

面对这种硬件,语言设计师选择允许我们选择允许我们选择适合硬件的类型的类型。

您可以声称这样的细节可以并且应该被抽象化,尤其是使用旨在在任何硬件上运行的语言。这可能会隐藏性能问题,但您可能是对的。只是没有那样发生。

Java实际上试图做到这一点。字节自动升为整数。当您第一次尝试进行任何严重的移位工作时,这一事实会让您发疯。

那为什么不能很好地工作呢?

过去,Java的最大卖点是您可以坐下来使用已知的优秀C算法,用Java键入它,并进行一些细微调整就可以使用。而且C与硬件非常接近。

保持这种状态并从整数类型中提取大小只是无法一起工作。

这样他们就可以了。他们只是没有。

也许程序员不希望某人能够使用比特定大小更大的数字,并且这使他们受到限制。

这是正确的想法。有执行此操作的方法。该钳位功能为一体。一种语言可以达到将任意界限烤成它们的类型的目的。并且在编译时知道这些界限时,可以优化这些数字的存储方式。

Java并不是那种语言。


一种语言可以达到将任意范围转换成它们的类型的目的。 ”实际上,Pascal具有这种形式的子范围类型。
彼得·泰勒

1

这些类型为何存在于Java中的一个重要原因很可能是简单且令人痛苦的非技术性原因:

C和C ++也具有这些类型!

尽管很难提供证明这是原因的证据,但是至少有一些有力的证据:Oak语言规范(0.2版)包含以下段落:

3.1整数类型

Oak语言的整数与C和C ++的整数类似,但有两个例外:所有整数类型都是与机器无关的,并且自引入C以来,某些传统定义已更改以反映世界的变化。四种整数类型的宽度分别为8位,16位,32位和64位,并且除非有unsigned修饰符加前缀,否则它们都必须带符号。

因此问题可以归结为:

为什么用C发明short,int和long?

对于此处提出的问题,我不确定字母问题的答案是否令人满意。但是,结合这里的其他答案,可能会很清楚,拥有这些类型可能是有益的(不管它们在Java中的存在是否仅仅是C / C ++的遗产)。

我能想到的最重要的原因是

  • 字节是最小的可寻址存储单元(如CandiedOrange所述)。A byte是数据的基本构建块,可以从文件中或通过网络读取。应该存在一些对此的显式表示(即使在某些情况下它有时是变相的,它也存在于大多数语言中)。

  • 的确,在实践中,使用单个类型表示所有字段和局部变量并称为该类型是有意义的int。关于stackoverflow有一个相关的问题:为什么Java API使用int而不是short或byte?。正如我在我的答案中提到的那样,使用较小类型(byteshort)的一个理由是,您可以创建这些类型的数组:Java具有数组的表示形式,该表示形式仍然“非常接近于硬件”。与其他语言相比(与对象数组(如Integer[n]数组)相反),int[n]数组不是引用的集合,这些引用的值分散在整个堆中。相反,它实际上是连续的n*4字节块-具有已知大小和数据布局的一块内存。当您选择将1000个字节存储在任意大小的整数值对象的集合中或一个byte[1000](需要1000个字节)中时,后者确实可以节省一些内存。(此方法的其他一些优点可能更微妙,并且只有在将Java与本机库接口时才会变得明显)


关于您具体询问的要点:

是否只能根据传入的数字大小来动态确定大小?

动态设置数据大小将意味着它也需要能够动态更改。这可能会导致性能问题吗?

如果考虑从头开始设计一种全新的编程语言,则有可能动态设置变量的大小。我不是编译器构建方面的专家,但是我认为,明智地管理动态变化类型的集合将非常困难,尤其是当您使用类型语言时。因此,可能会将所有数字归结为存储在“通用,任意精度数字数据类型”中,这肯定会对性能产生影响。当然,还有编程即是强类型和/或提供任意大小的数种语言,但我不认为存在着走到这样一个真正的通用编程语言。


旁注:

  • 您可能想知道unsignedOak规范中提到的修饰符。实际上,它还包含一个备注:unsigned尚未实现;可能永远不会实现。” 。他们是对的。

  • 除了想知道为什么C / C ++完全具有这些不同的整数类型外,您还可能想知道为什么它们将它们如此混乱地弄乱,以至于您永远不知道它们int有多少位。这样做的理由通常与性能有关,可以在其他地方查找。


0

它肯定表明您尚未了解性能和体系结构。

  • 首先,并不是每个处理器都可以处理大型类型,因此,您需要了解限制并进行处理。
  • 其次,较小的类型意味着执行操作时具有更高的性能。
  • 同样,大小很重要,如果您必须将数据存储在文件或数据库中,那么大小将影响性能以及所有数据的最终大小,例如,假设您有一个包含15列的表,最后会有几列数百万条记录。为每列选择一个较小的必要大小或仅选择最大的类型之间的差异,是操作性能中可能的数据量和时间差异。
  • 此外,它还适用于复杂的计算,在这种计算中,正在处理的数据大小将产生很大的影响,例如在游戏中。

忽略数据大小的重要性始终会影响性能,您必须使用所需的尽可能多的资源,但永远不要使用更多资源!

这就是程序或系统之间的区别,它们实际上做的很简单,但是效率很低,需要大量资源,并且使用该系统的成本很高。或功能强大但运行速度比其他系统便宜的系统。


0

有几个很好的理由

(1)虽然一个字节变量对一长字节的存储是微不足道的,但数组中数百万个的存储却非常重要。

(2)基于特定整数大小的“硬件本机”算法可能效率更高,对于某些平台上的某些算法而言,这可能很重要。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.