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

在Java中, byteshortintlong 都有原始类型,也是如此floatdouble 。为什么有必要让一个人设置原始值应该使用多少字节?不能根据传入的数量大小动态确定大小吗?

我能想到的原因有两个:

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

我仍然认为通过简单地使用单个 intfloat 类型可以获得很多好处,Java是否有特定原因决定不采用这种方式?

18
@gardenhead这是正交的,是的,但是......只考虑你想用Java编写的服务器和用C编写的客户端之间进行通信的情况。当然这个可以用专用的基础设施来解决。例如。有一些内容,如 developers.google.com/protocol-buffers 。但这是一个大型的大锤,用于通过网络传输整数的小螺母。 (我知道,这不是一个强有力的论据,但可能需要考虑 - 讨论细节超出了评论的范围)。
额外 作者 Shrey,
@Paparazzi现有的编程语言和执行环境(编译器,解释器等)将根据实际值的大小(例如,加法运算的结果)存储动态宽度整数。结果是:在CPU上执行的代码变得更加复杂;该整数的大小变为动态;从内存中读取动态宽度整数可能需要多次行程;结构(对象)和在其字段/元素内包含动态宽度整数的数组也可以具有动态大小。
额外 作者 Ryan Cox,
要了解动态宽度整数的实现方式,请阅读标记指针表示和标记整数
额外 作者 Ryan Cox,
对于downvoters,我想补充一点,这个问题与编译器研究人员希望回答的问题
额外 作者 Ryan Cox,
@tofro我不明白。只需以您喜欢的任何格式发送数字:十进制,二进制等。序列化是一个完全正交的问题。
额外 作者 gardenhead,
除了其他答案之外,还有一种情况是您可能希望依赖特定的溢出行为。
额外 作者 SK-logic,
您将如何通过网络连接发送“动态大小适应整数”?你会如何将它存储到文件中?
额外 作者 tofro,
那么如果你添加到一个数字,你认为应该动态改变类型?我甚至想要改变类型吗?如果数字初始化为intUnknown alpha = a + b;你觉得编译器有点困难吗?为什么这个特定于java?
额外 作者 Flamewires,

7 答案

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

备择方案

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

空间限制

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

效率

即使我们忽略空间,我们仍然受到CPU的限制,CPU只有指令以固定的字节数运行(64位架构上的8个字节)。这意味着即使提供单个8字节 long 类型,也可以使语言的实现比具有无界自然数字类型简单明了,因为它能够将算术运算直接映射到 single 底层CPU指令。如果允许程序员使用任意大数,那么必须将单个算术运算映射到复杂机器指令的序列,这会降低程序的速度。这是你提出的第(1)点。

浮点类型

The discussion so far has only concerned integers. 浮点类型 are a complex beast, with extremely subtle semantics and edge-cases. Thus, even though we could easily replace int, long, short, and byte with a single nat type, it is not clear what the type of floating-point numbers even is. They aren't real numbers, obviously, as real numbers cannot exist in a programming language. They aren't quite rational numbers, either (though it's straight-forward to create a rational type if desired). Basically, IEEE decided on a way to kinda sorta approximate real numbers, and all languages (and programmers) have been stuck with them ever since.

最后:

也许程序员不希望有人能够使用比特定大小更大的数字,这可以让他们限制它。

这不是一个有效的理由。首先,我无法想到任何类型可以自然地编码数字边界的情况,更不用说程序员想要强制执行的界限与任何基本类型的大小完全对应的机会是天文数字低。

15
额外
作为我之前评论的一个更具体的例子,WAV格式有许多无符号和短路的字段。声音样本本身的一种格式是为每个样本签名。一个显示WAV文件结构的文档在这里: soundfile.sapp.org/doc/WaveFormat</一>
额外 作者 Vincent Buck,
我觉得这个缺失非常重要:易于与其他系统兼容。例如,假设您的Java程序需要能够定义一些低级数据包格式,并且此格式要求您包含无符号的16位数。大多数语言的用户。没有这种类型的Java缺乏额外的工作来转换数字以适应这个预期值并确保你没有任何截断。我怀疑这至少是C#的一部分原因,尽管受到Java的启发,它仍然包含无符号类型(由于某种原因Java被排除在外)。
额外 作者 Vincent Buck,
哦,整洁。所以它有类型改进。现在我需要检查一下。
额外 作者 gardenhead,
我不熟悉阿达。我可以将整数限制为任何类型,例如输入my_type = int(7,2343)
额外 作者 gardenhead,
@Devsman这怎么自然?
额外 作者 gardenhead,
@jk依赖类型的语言仍然非常罕见,Java没有它们。另外我不明白你对枚举的评论?
额外 作者 gardenhead,
枚举不等于整数。枚举只是sum-types的一种使用模式。某些语言透明地将枚举编码为整数这一事实是一种语言缺陷,而不是可利用的特性。
额外 作者 gardenhead,
枚举{a = 1,b = 2,c = 3}是一个只有3个值的整数(诚然,某些语言并不强制执行此操作)
额外 作者 kraftydevil,
除非该范围非常小,否则在编码数字范围时,清楚地说enums会有很多不足之处
额外 作者 kraftydevil,
它们相当于自然数的一个子集。是的,它们是单位类型的和类型,这是为什么它们相当于Nat的子集(认为类型1 + 1 + 1等)。此名称来自可枚举,字面上定义为等同于Nat
额外 作者 kraftydevil,
在一种类型中编码数字边界绝对确实发生在依赖类型语言中,并且在较小程度上发生在其他语言中,例如作为枚举
额外 作者 kraftydevil,
我们拥有花车这一事实的关键在于我们为他们提供专用硬件
额外 作者 kraftydevil,
首先,我无法想到任何类型可以自然地编码数字界限的情况 Ada是这种类型的野兽。类型Two_Digit的范围是10..99; type Three_Digit的范围是100..999;
额外 作者 Devsman,
@gardenhead我可能误解了你的意思“天生”。我的意思是程序员可以定义类型,以便Ada将对它们进行边界检查,并且他们不必检查是否(i> 99)并手动引发异常。
额外 作者 Devsman,
是的。语法如下:type my_type是range 7..2343
额外 作者 Devsman,

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

  1. 原生数据类型:语言的数据类型越接近硬件的基础数据类型,语言的效率就越高。 (从某种意义上说,你的程序必然是高效的,但从某种意义上说,如果你真的知道自己在做什么,那么编写运行效率与硬件运行效率相当的代码。)提供的数据类型通过Java对应于最流行的硬件的字节,单词,双字和四字。这是最有效的方式。

  2. 32位系统上无根据的开销:如果决定将所有内容映射到固定大小的64位长,这将对32-施加巨大的惩罚位架构需要比32位操作执行64位操作要多得多的时钟周期。

  3. 内存浪费:有很多硬件对内存对齐不太挑剔,(英特尔x86和x64架构就是这样的例子),所以一个数组该硬件上的100个字节只能占用100个字节的内存。但是,如果你不再有一个字节,而你必须使用long,那么相同的数组将占用更多的内存。字节数组很常见。

  4. 计算数字大小:您根据传入的数量动态确定整数大小的概念太简单了;没有单一的“传入”数字;计算需要在运行时执行的数量,在每个可能需要更大尺寸结果的操作上:每次增加一个数字,每次添加两个数字,每次乘以两个数字等

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

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

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

9
额外

为了避免重复其他答案中讨论的要点,我将尝试概述多个视角。

从语言设计角度来看

  • It is certainly possible to design and implement a programming language and its execution environment that will automatically accommodate the results of integer operations that don't fit in the machine width.
  • It is the language designer's choice whether to make such dynamic-width integers to be the default integer type for this language.
  • However, the language designer has to consider the following drawbacks:
    • The CPU will have to execute more code, which takes more time. However, it is possible to optimize for the most frequent case in which the integer fits within a single machine word. See tagged pointer representation.
    • The size of that integer becomes dynamic.
    • Reading a dynamic width integer from memory may require more than one trip.
    • Structs (objects) and arrays that contain dynamic width integers inside their fields/elements will have a total (occupied) size that is dynamic as well.

历史原因

这已经在维基百科关于Java历史的文章中讨论过,并且在 Marco13的回答中也进行了简要讨论。

我会指出:

  • 语言设计师必须兼顾审美和务实的心态。审美心态想要设计一种不易出现众所周知问题的语言,例如整数溢出。务实的思维方式提醒设计人员,编程语言需要足够好以实现有用的软件应用程序,并与其他以不同语言实现的软件部分互操作。
  • 打算从较旧的编程语言中获取市场份额的编程语言可能更倾向于务实。一个可能的后果是他们更愿意合并或借用那些旧语言的现有编程结构和样式。

效率原因

什么时候效率很重要?

  • 当您打算将编程语言宣传为适合开发大型应用程序时。
  • 当你需要处理数以百万计的小件物品时,每一点效率都会增加。
  • 当你需要与另一种编程语言竞争时,你的语言需要表现得体面 - 它不一定是最好的,但它肯定有助于保持接近最佳性能。

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

  • 计算机内存曾经是稀缺资源。在过去,可以由计算机处理的应用程序数据的大小受到计算机内存量的限制,尽管可以使用巧妙的编程(实现成本更高)来解决这个问题。

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

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

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

  • 这些需求经常出现在软件库的开发中,包括语言自己的标准库。以下是几个这样的案例。

互操作性</强>

  • Often, higher-level programming languages need to interact with the operating system, or pieces of software (libraries) written in other lower-level languages. These lower-level languages often communicate using "structs", which is a rigid specification of the memory layout of a record consisting of fields of different types.
  • For example, a higher-level language may need to specify that a certain foreign function accepts a char array of size 256. (Example.)
  • Some abstractions used by operating systems and file systems require the use of byte streams.
  • Some programming languages choose to provide utility functions (e.g. BitConverter) to help the packing and unpacking of narrow integers into bit-streams and byte-streams.
  • In these cases, the narrower integer types need not be a primitive type built into the language. Instead, they can be provided as a library type.

字符串处理

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

文件格式处理

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

可取性,软件质量和程序员的责任

  • 对于许多类型的应用程序,自动加宽整数实际上并不是一个理想的特性。既不是饱和也不是环绕(模数)。
  • 许多类型的应用程序将受益于程序员明确规定软件中各个关键点的最大允许值,例如API级别。

请考虑以下情形。

  • 软件API接受JSON请求。该请求包含一系列子请求。可以使用Deflate算法压缩整个JSON请求。
  • 恶意用户创建包含10亿个子请求的JSON请求。所有儿童请求都是相同的;恶意用户希望系统烧掉一些做无用工作的CPU周期。由于压缩,这些相同的子请求被压缩到非常小的总大小。
  • 很明显,对数据压缩大小的预定义限制是不够的。相反,API需要对可包含在其中的子请求数量施加预定义的限制,和/或对数据缩小的大小的预定义限制。

通常,必须为此目的设计能够安全地扩展许多数量级的软件,并且复杂性会增加。即使消除了整数溢出问题,它也不会自动出现。这是一个回答语言设计视角的完整循环:通常,当发生意外的整数溢出(通过抛出错误或异常)时拒绝执行工作的软件优于自动符合天文大型操作的软件。

这意味着OP的观点,

为什么有必要让一个人设置原始值应该使用多少字节?

是不正确的。应允许程序员(有时需要)在软件的关键部分指定整数值可以采用的最大 幅度 。正如 gardenhead的答案所指出的那样,原始类型所施加的自然限制对此无用;语言必须为程序员提供声明量级和强制执行此类限制的方法。

6
额外

这一切都来自硬件。

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

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

一个字节是8位。有了它,你可以表达8个布尔,但你不能一次只查找一个。你解决1,你解决所有8。

它曾经是那么简单,但后来我们从8位总线转到16,32,现在是64位总线。

这意味着虽然我们仍然可以在字节级别进行寻址,但我们无法从内存中检索单个字节而无需获取其相邻字节。

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

你可以声称这样的细节可以而且应该被抽象掉,特别是在一种旨在在任何硬件上运行的语言中。这可能会隐藏性能问题,但您可能是对的。它不是那样发生的。

Java实际上试图这样做。字节自动提升为Ints。当你第一次尝试在其中进行任何严重的位移工作时,这个事实会让你疯狂。

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

Java是当天的一大卖点,你可以坐下来使用已知的优秀C算法,在Java中输入它,并通过微调调整就可以了。 C非常接近硬件。

保持这种状态并从整体类型中抽象出大小只是不起作用。

所以他们可以。他们只是没有。

也许程序员不希望有人能够使用比特定大小更大的数字,这可以让他们限制它。

这是有效的思考。有这样做的方法。一个钳制功能。一种语言甚至可以将任意界限加入其类型中。当在编译时知道这些边界时,可以优化这些数字的存储方式。

Java就不是那种语言了。

2
额外
一种语言甚至可以将任意边界加入到它们的类型中”事实上,Pascal有一种具有子范围类型的形式。
额外 作者 TobiX,

可能,Java中存在这些类型的一个重要原因是简单且令人沮丧的非技术性:

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

虽然很难提供证明这就是原因,但至少有一些有力的证据:橡树语言规范(版本0.2)包含以下段落:

3.1整数类型

     

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

所以问题可以归结为:

为什么短期,国内和长期发明在C?

我不确定在这里提出的问题的背景下,信件问题的答案是否令人满意。但是结合这里的其他答案,可能会发现拥有这些类型(无论它们在Java中的存在是否只是C/C ++的遗留)都是有益的。

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

  • 字节是最小的可寻址存储器单元(如已提到的CandiedOrange)。 byte 是数据的基本构建块,可以从文件或网络中读取。 一些的显式表示应该存在(并且它确实存在于大多数语言中,即使它有时会伪装)。

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


关于您特别询问的要点:

根据传入的数量有多大,动态确定大小不是吗?

     

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

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


附注:

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

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

1
额外

它肯定表明你还没有教过性能和架构。

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

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

这是一个程序或系统之间的区别,它做了非常简单的事情,并且需要大量的资源,并且使得该系统的使用成本非常高,令人难以置信的低效率;或者是一个可以做很多事情的系统,但运行速度比其他系统快,并且运行起来非常便宜。

0
额外

有几个很好的理由

(1)虽然一个字节变量的存储与一个长的存储是微不足道的, 在阵列中存储数百万是非常重要的。

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

0
额外