数组对象是否显式包含索引?

2024-01-12

从学习 Java 的第一天起,各种网站和许多老师就告诉我,数组是连续的内存位置,可以存储指定数量的相同类型的数据。

由于数组是一个对象,并且对象引用存储在堆栈中,而实际对象位于堆中,因此对象引用指向实际对象。

但是当我遇到如何在内存中创建数组的示例时,它们总是显示如下内容:

(其中对数组对象的引用存储在堆栈上,该引用指向堆中的实际对象,其中还有指向特定内存位置的显式索引)

但最近我遇到了网上笔记 http://chortle.ccsu.edu/java5/Notes/chap49C/ch49C_4.html#array,_row_and_column_numbersJava 中他们指出数组的显式索引未在内存中指定。编译器只是通过在运行时查看提供的数组索引号来知道去哪里。

像这样:

看完笔记后,我也在Google上搜索了这件事,但这个问题的内容要么很模糊,要么根本不存在。

我需要对此事进行更多澄清。数组对象索引是否显式显示在内存中?如果不是,那么 Java 如何在运行时管理命令以转到数组中的特定位置?


数组对象是否显式包含索引?

简短回答: No.

更长的答案:通常不会,但理论上可以。

完整答案:

Java 语言规范和 Java 虚拟机规范都没有规定any保证数组如何在内部实现。它所需要的只是数组元素可以通过int索引号的值来自0 to length-1。实现如何实际获取或存储这些索引元素的值是实现私有的细节。

一个完全一致的 JVM 可以使用哈希表 https://en.wikipedia.org/wiki/Hash_table来实现数组。在这种情况下,元素将是不连续的,分散在内存中,并且它would需要记录元素的索引,以了解它们是什么。或者它可以向月球上的一个人发送消息,后者将数组值写在贴有标签的纸上,并将它们存储在许多小文件柜中。我不明白为什么 JVM 会想要做这些事情,但它可以。

实践中会发生什么?典型的 JVM 会将数组元素的存储分配为平坦、连续的内存块。定位特定元素很简单:将每个元素的固定内存大小乘以所需元素的索引,并将其添加到数组开头的内存地址:(index * elementSize) + startOfArray。这意味着数组存储只包含原始元素值,按索引连续排序。没有必要同时存储每个元素的索引值,因为内存中元素的地址暗示了它的索引,反之亦然。但是,我不认为您显示的图表试图表明它显式存储了索引。该图只是简单地标记了图上的元素,以便您知道它们是什么。

使用连续存储并通过公式计算元素地址的技术简单且极其快速。它的内存开销也很小,假设程序只分配它们真正需要的大小的数组。程序依赖于并期望数组的特定性能特征,因此对数组存储执行一些奇怪操作的 JVM 可能会表现不佳并且不受欢迎。所以实际的JVM 将被限制为实现连续存储或执行类似操作的存储。

我只能想到该方案的几个有用的变体:

  1. 堆栈分配或寄存器分配的数组:在优化期间,JVM 可能会通过以下方式确定逃逸分析 https://en.wikipedia.org/wiki/Escape_analysis数组仅在一个方法中使用,如果数组也是一个较小的固定大小,那么它将是直接在堆栈上分配的理想候选对象,计算相对于堆栈指针的元素地址。如果数组非常小(固定大小可能最多 4 个元素),JVM 可以更进一步,将元素直接存储在 CPU 寄存器中,所有元素访问都展开并硬编码。

  2. 压缩布尔数组:计算机上最小的直接可寻址内存单元通常是 8 位字节。这意味着,如果 JVM 对每个布尔元素使用一个字节,则布尔数组会浪费每 8 位中的 7 位。如果布尔值在内存中打包在一起,每个元素将仅使用 1 位。通常不会进行这种打包,因为提取字节的各个位速度较慢,并且需要特别考虑以保证多线程的安全。然而,打包布尔数组在某些内存受限的嵌入式设备中可能非常有意义。

尽管如此,这两种变体都不需要每个元素存储自己的索引。

我想谈谈你提到的其他一些细节:

数组存储指定数量的相同类型的数据

Correct.

事实上,数组的所有元素都是相同的type很重要,因为这意味着所有元素都是相同的size在记忆中。这就是允许通过简单地乘以元素的共同大小来定位元素的原因。

如果数组元素类型是引用类型,这在技术上仍然成立。尽管在这种情况下,每个元素的值不是对象本身(其大小可能不同),而只是引用对象的地址。此外,在这种情况下,数组的每个元素引用的对象的实际运行时类型可以是该元素类型的任何子类。例如。,

Object[] a = new Object[4]; // array whose element type is Object
// element 0 is a reference to a String (which is a subclass of Object)
a[0] = "foo";

// element 1 is a reference to a Double (which is a subclass of Object)
a[1] = 123.45;

// element 2 is the value null (no object! although null is still assignable to Object type)
a[2] = null;

// element 3 is a reference to another array (all arrays classes are subclasses of Object)
a[3] = new int[] { 2, 3, 5, 7, 11 };

数组是连续的内存位置

如上所述,这不一定是真的,尽管在实践中几乎肯定是真的。

更进一步,请注意,尽管 JVM 可能会从操作系统分配一块连续的内存块,但这并不意味着它最终会是连续的。物理内存。操作系统可以给程序一个虚拟地址空间 https://en.wikipedia.org/wiki/Virtual_memory它的行为就像是连续的,但各个内存页面分散在不同的地方,包括物理 RAM、磁盘上的交换文件,或者如果已知其内容当前为空白,则根据需要重新生成。即使虚拟内存空间的页面驻留在物理 RAM 中,它们也可以以任意顺序排列在物理 RAM 中,具有复杂的页表定义从虚拟地址到物理地址的映射。即使操作系统认为它正在处理“物理 RAM”,它仍然可以在模拟器中运行。我的观点是,可以有一层又一层的层,并深入到它们的底部all https://en.wikipedia.org/wiki/Simulism要弄清楚到底发生了什么需要一段时间!

编程语言规范的部分目的是将明显的行为来自实施细节。编程时,您通常可以单独按照规范进行编程,而不必担心其内部如何发生。然而,当您需要处理有限速度和内存的现实限制时,实现细节就变得相关了。

由于数组是一个对象,并且对象引用存储在堆栈中,而实际对象位于堆中,因此对象引用指向实际对象

这是正确的,除了你所说的关于堆栈的内容。对象引用can存储在堆栈上(作为局部变量),但它们可以also存储为静态字段或实例字段,或存储为数组元素,如上例所示。

另外,正如我之前提到的,聪明的实现有时可以直接在堆栈或 CPU 寄存器中分配对象作为优化,尽管这对程序的明显行为影响为零,仅影响性能。

编译器只是通过在运行时查看提供的数组索引号来知道去哪里。

在 Java 中,执行此操作的不是编译器,而是虚拟机。数组是JVM本身的一个特性 https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.9,因此编译器可以将使用数组的源代码简单地转换为使用数组的字节码。那么JVM的工作就是决定如何实现数组,而编译器既不知道也不关心它们是如何工作的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数组对象是否显式包含索引? 的相关文章

随机推荐