从学习Java的第一天起,各种网站和许多老师就告诉我,数组是连续的内存位置,可以存储指定数量的相同类型的数据。
由于数组是一个对象,对象引用存储在堆栈上,而实际对象位于堆中,因此对象引用指向实际对象。
但是当我遇到如何在内存中创建数组的示例时,它们总是显示如下内容:
(其中对数组对象的引用存储在堆栈上,并且该引用指向堆中的实际对象,其中还有指向特定内存位置的显式索引)
但是最近我在网上看到了Java的注释,其中指出数组的显式索引没有在内存中指定。编译器只需在运行时查看所提供的数组索引号就知道该去哪里。
就像这样:
看完笔记后,我也在谷歌上搜索了这个问题,但关于这个问题的内容要么很模糊,要么根本不存在。
我需要更多关于这个问题的澄清。数组对象索引是否在内存中显式显示?如果没有,那么Java如何管理命令以在运行时转到数组中的特定位置?
如你所说,数组只存储相同类型的对象。每种类型都有相应的大小,以字节为单位。例如,在< code>int[]中,每个元素将占用4个字节,< code>byte[]中的每个< code>byte将占用1个字节,< code>Object[]中的每个< code>Object将占用1个字(因为它实际上是指向堆的指针),等等。
重要的是,每个类型都有大小,每个数组都有类型。
然后,我们讨论在运行时将索引映射到内存位置的问题。这实际上非常简单,因为您知道数组的起始位置,并且给定数组的类型,您知道每个元素的大小。
如果您的数组从某个内存位置N开始,您可以使用给定的索引I和元素大小S来计算您要查找的内存将位于内存地址N(S*I)。
这就是Java如何在运行时找到索引的内存位置,而不存储它们。
在Java中,数组是对象。参见JLS-第10章。数组:
在Java编程语言中,数组是对象(第4.3.1节),是动态创建的,并且可以分配给Object类型的变量(第4.3.2节)。类Object
的所有方法都可以在数组上调用。
如果您查看10.7。数组成员章节,您将看到索引不是数组成员的一部分:
数组类型的成员如下:
公共final
字段length
,其中包含数组的组件数。长度可以为正或零。
>
公共
方法clone
,它覆盖类Object
中同名的方法,并且不抛出检查异常。数组类型T[]
的clone方法的返回类型是T[]
。
从类对象
继承的所有成员;对象唯一未继承的方法是其克隆方法。
由于每个类型的大小都是已知的,因此可以很容易地确定阵列中每个组件的位置(给定第一个组件)。
访问元素的复杂度为O(1),因为它只需要计算地址偏移量。值得一提的是,并非所有编程语言都假定这种行为。
数组对象是否显式包含索引?
简短的回答:不可以。
更长的答案是:通常不会,但理论上可以。
完整答案:
Java语言规范和Java虚拟机规范都没有对数组如何在内部实现做出任何保证。它只需要通过int
索引号访问数组元素,索引号的值从0
到rende-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的工作,编译器既不知道也不关心它们是如何工作的。
这是一个简化的示例,但假设我想在100x100网格上生成5个唯一位置。这些位置将存储在数组[[x, y],…]中。 尝试了生成随机 x 和 y 并检查数组 [x, y] 是否已经在结果数组中的明显方法。如果是,则生成不同的值,如果不生成,则将其添加到结果数组中。 但是,这将永远不会找到重复项,因为数组在技术上是不同的对象。那么,检测数组是否包含“相等”数组/对象的首选方法是什么?
问题内容: 我再次使用swift数组和NSArray仅提供的containsObject! 我将swift数组桥接到NSArray来执行以下操作: 它通常可以正常工作,但是只要我放一个String就可以了!在字符串类型的数组中,它崩溃。即使containsObject确实占用了AnyObject! 声明一个字符串!数组也无济于事 但是一样没有效果很好 那么我该如何显式包装东西?我真的不明白为什么我
问题内容: 好的,所以我试图从我用来学习Java的书中进行练习。这是我到目前为止的代码: 以下是确切措词的练习: 修改类Calculator.java,以将所有数字按钮保留在声明为10个元素的数组中,如下所示: 替换从10开始的行 带有创建按钮并将其存储在此数组中的循环。 好的,所以我尝试使用声明数组,但这给了我错误: 这行有多个标记- 按钮无法解析为类型- 按钮无法解析为类型 相反,我尝试了这个
问题内容: 说我的课很简单 我希望将此类s 的集合存储在People类的属性中,该属性是类型为Person的数组 也许我做到这一点如下 问题:请问我如何检查people.list是否包含实例alex? 我很想尝试的简单尝试 称一个错误 问题答案: 有两个功能: 编译器在抱怨是因为编译器知道不是,因此需要一个谓词,但不是谓词。 如果阵列中的人员是(不是),则可以使用: 由于它们不相等,因此可以将第二
问题内容: 我需要确定数组中是否存在值。 我正在使用以下功能: 上面的函数总是返回false。 数组值和函数调用如下: 问题答案: 你可以像这样使用它:
问题内容: 在oracle数据库表中,我需要查找给定批号的结果。保存批号的字段是一个包含“ 1-3,5,10-15,20”之类的字符串(此字符串内的数字已排序) 有没有办法做到这一点? 在上面的示例中,应该找到以下批号的结果: 在应用程序中无法做到这一点,因此必须在数据库内部完成。 类似于:“ SELECT * FROM产品的批次= 2” 问题答案: 通过使用REGEXP_SUBSTR函数和分层查