当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《数组对象是否显式包含索引?》经验,为你挑选了7个好方法。

从学习Java的第一天起,各个网站和许多老师都告诉我,阵列是连续的内存位置,可以存储所有相同类型的指定数量的数据.

由于数组是对象,对象引用存储在堆栈中,而实际对象存在于堆中,因此对象引用指向实际对象.

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

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

各种网站和教师讲授的对象的内部工作

但是最近我遇到了Java的在线笔记,其中他们声明数组的显式索引没有在内存中指定.编译器只是通过在运行时查看提供的数组索引号来知道去哪里.

像这样:

在此输入图像描述

在阅读了笔记之后,我还在Google上搜索了这个问题,但是这个问题的内容要么很模糊,要么根本不存在.

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



1> Maroun..:

在Java中,数组是对象.请参阅JLS - 第10章.数组:

在Java编程语言中,数组是对象(§4.3.1),是动态创建的,可以分配给Object类型的变量(§4.3.2).Object可以在数组上调用类的所有方法.

如果你看10.7.在Array Members章节中,您将看到索引不是数组成员的一部分:

数组类型的成员是以下所有成员:

public final字段length,包含数组的组件数.长度可以是正数或零.

public方法clone在类中重写相同名称的方法,Object并且不会抛出任何已检查的异常.数组类型的clone方法的返回类型T[]T[].

所有成员都继承自班级Object; 唯一没有继承的Object方法是它的clone方法.

由于每种类型的大小都是已知的,因此您可以轻松确定阵列中每个组件的位置,并给出第一个组件的位置.

访问元素的复杂性是O(1),因为它只需要计算地址偏移量.值得一提的是,并非假设所有编程语言都存在这种行为.



2> Boann..:

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

简答:不.

更长的答案:通常不是,但理论上可以做到.

完整答案:

Java语言规范和Java虚拟机规范都没有对如何在内部实现数组做出任何保证.它需要的是数组元素由int索引号访问,索引号的值为0to length-1.实现如何实际获取或存储这些索引元素的值是实现的私有细节.

完全一致的JVM可以使用哈希表来实现数组.在这种情况下,该元素将是不连续的,分散在各地的内存,它需要记录元素的索引,要知道它们是什么.或者它可以向月球上的男人发送消息,他将数组值写在标签纸上并将它们存储在许多小文件柜中.我不明白为什么JVM会想要做这些事情,但它可以.

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

使用连续存储并通过公式计算元素地址的技术简单且非常快速.它还具有非常小的内存开销,假设程序只将它们的数组分配到它们真正需要的大小.程序依赖于并期望数组的特定性能特征,因此对阵列存储执行奇怪操作的JVM可能表现不佳并且不受欢迎.因此,实用的 JVM将被限制为实现连续存储,或者执行类似的操作.

我只能想到有关该方案的几个变体,它们将会非常有用:

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

    打包的布尔数组:计算机上最小的可直接寻址的内存单元通常是一个8位字节.这意味着如果JVM对每个布尔元素使用一个字节,那么布尔数组会每8位中浪费7个.如果布尔值在内存中打包在一起,那么每个元素只使用1位.这种打包通常不会完成,因为提取单个字节位的速度较慢,需要特别注意多线程才能安全.但是,在一些受内存限制的嵌入式设备中,压缩布尔数组可能非常有意义.

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

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

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

正确.

所有数组元素都是相同类型的事实很重要,因为它意味着所有元素在内存中的大小相同.这就是通过简单地乘以它们的共同大小来定位元素的原因.

如果数组元素类型是引用类型,那么这在技术上仍然是正确的.虽然在这种情况下,每个元素的值不是对象本身(可能具有不同的大小),而只是引用对象的地址.此外,在这种情况下,数组的每个元素引用的实际运行时类型的对象可以是元素类型的任何子类.例如,

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可能会从操作系统中分配一块连续的内存,但这并不意味着它最终会在物理RAM中连续存在.操作系统可以为程序提供一个虚拟地址空间,其行为就像是连续的,但是各个页面的内存分散在各个地方,包括物理RAM,磁盘上的交换文件,或者如果已知其内容当前为空,则根据需要重新生成.即使虚拟存储器空间的页面驻留在物理RAM中,它们也可以以任意顺序排列在物理RAM中,其中复杂的页表定义了从虚拟地址到物理地址的映射.即使操作系统认为它处理的是"物理RAM",它仍然可以在模拟器中运行.我可以分层次地层层叠叠,然后深入了解它们的所有内容,以确定真正发生的事情需要一段时间!

编程语言规范的部分目的是将明显的行为实现细节分开.在编程时,您通常可以单独编程到规范,而不必担心内部如何发生.然而,当您需要处理有限速度和内存的实际约束时,实现细节变得相关.

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

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

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

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

在Java中,执行此操作的不是编译器,而是虚拟机.数组是JVM本身的一个特性,因此编译器可以将使用数组的源代码转换为使用数组的字节码.然后是JVM决定如何实现数组的工作,编译器既不知道也不关心它们是如何工作的.



3> William Rose..:

如你所说,数组只存储相同类型的对象.每种类型都有相应的大小,以字节为单位.例如在int[]每个元件将占据4个字节,每byte一个byte[]将占用1个字节,每个ObjectObject[]将占据1个字(因为它是真正的指针堆)等.

重要的是每个类型都有一个大小,每个数组都有一个类型.

然后,我们遇到了在运行时将索引映射到内存位置的问题.它实际上非常简单,因为您知道数组的起始位置,并且根据数组的类型,您知道每个元素的大小.

如果您的数组从某个内存位置N开始,您可以使用给定的索引I和元素大小S来计算您要查找的内存将位于内存地址N +(S*I).

这就是Java在运行时查找索引的内存位置而不存储它们的方式.



4> Thomas Kläge..:

在您的第一张照片arr[0],以arr[4]不数组元素的引用.它们只是该位置的说明性标签.



5> Viktor Toth..:

除了严格用于人类消费的标签外,您的两个图表是相同且相同的.

也就是说,第一图中,标签arr[0],arr[1]等,不在阵列的一部分.它们仅用于说明目的,指示数组元素如何在内存中布局.

你被告知,即数组存储在内存中的连续位置(至少在涉及虚拟地址的情况下;在现代硬件架构上,这些不需要映射到连续的物理地址),并且数组元素根据它们的大小和索引来定位, 是正确的.(至少在...中,它在C/C++中肯定是正确的.在大多数(如果不是全部)Java实现中几乎肯定是正确的.但是在允许稀疏数组或可以增长的数组的语言中它可能是不正确的或动态缩小.)

在堆栈中创建数组引用而将数组数据放在堆上的事实是特定于实现的细节.将Java直接编译为机器代码的编译器可以不同地实现阵列存储,同时考虑目标硬件平台的特定特征.事实上,一个聪明的编译器可能会将整个堆栈中的小数组放在一起,并且只将堆用于较大的数组,以最大限度地减少垃圾收集的需要,从而影响性能.



6> atao..:

数组的引用并不总是在堆栈上.如果它是类的成员,它也可以存储在堆上.

数组本身可以包含原始值或对象的引用.无论如何,数组的数据总是相同的.然后编译器可以在没有显式指针的情况下处理它们的位置,只能使用值/引用大小和索引.

请参阅:
*Java语言规范,Java SE 8版 - 数组
*Java虚拟机规范,Java SE 8版 - 参考类型和值



7> Madhusudana ..:

要理解的关键部分是为阵列分配的内存是连续的.因此,给定数组的初始元素的地址,即arr [0],这种连续的内存分配方案有助于运行时在给定其索引的情况下确定数组元素的地址.

假设我们声明了int [] arr = new int [5],并且它的初始数组元素arr [0]位于地址100.要到达数组中的第三个元素,运行时需要执行的只是数学100 + ((3-1)*32) = 164(假设32是整数的大小).所以运行时需要的只是该数组的初始元素的地址.它可以根据索引和数组存储的数据类型的大小派生出数组元素的所有其他地址.

只是一个偏离主题的注释:虽然阵列占据了连续的内存位置,但地址只在虚拟地址空间而不是在物理地址空间中是连续的.一个巨大的数组可能跨越多个可能不连续的物理页面,但数组使用的虚拟地址将是连续的.并且虚拟地址到物理地址的映射由OS页表完成.

推荐阅读
郑谊099_448
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有