Java中的数组是存储数据还是指针?

3
我正在研究数据本地化,并希望将其用于改善我正在编写的游戏引擎。假设我已经在不同的时间创建了五个对象,它们现在都在内存中的不同位置,而不是相邻的位置。如果我将它们全部添加到数组中,那么该数组是否仅包含指向这些对象的指针,并且它们将保持在内存中的同一位置,或者将它们全部添加到数组中会重新排列它们并使它们连续?我问这个问题是因为我认为使用数组是使它们连续的好方法,但我不知道数组是否能解决我的问题!

一个数组 T[] 只保存对象引用。 - Ousmane D.
1
这有99.99%的概率是一种过早优化。努力使您的代码可读且稳定,了解数据结构的时间复杂度,并注意IO。仅在出现性能问题且已证明局部性是问题的情况下进行优化。对于5个对象,这不会是问题。 - JB Nizet
@JBNizet 我并没有说那里只有5个对象,那只是一个示例来说明我的意思,但还是谢谢你的回答。 - Joza100
你正在创建一个游戏引擎,但不知道JVM如何管理内存? - Bhesh Gurung
@BheshGurung 只是因为我不知道事物如何存储,并不意味着我不很了解Java -_- 一个有帮助的答案,谢谢。 - Joza100
3个回答

9

tl;dr

操作对象引用数组不会影响对象本身,也不会影响对象在内存中的位置。

对象

一个对象的数组实际上是一个对象引用(指针)的数组。指针是指向内存中另一个位置的地址。

我们说数组中保存了对象,但这并不准确。因为Java不会将指针本身暴露给程序员,所以我们通常不知道它们的存在。当我们访问数组中的元素时,实际上是检索指针,但Java立即跟随该指针以定位内存中的对象。

这种自动查找指针到对象的过程,使得指针数组感觉像是一个对象数组。Java程序员认为她的数组保存了她的对象,而实际上对象离数组只有一步跳和跳之遥。

Java 中的数组是作为连续的内存块实现的。对于对象数组,这些对象的指针被存储在连续的内存中。但当我们访问元素时,我们要跳转到内存中的另一个位置来访问我们想要的实际对象。

enter image description here

添加元素可能是“便宜”的,因为如果内存恰好在相邻的内存中可用,可以将其分配给数组以腾出更多元素的空间。但实际上这是不太可能的。很可能需要在其他地方建立一个新数组,并将所有指针复制到新数组中,然后丢弃原始数组。
这种新数组和复制操作是“昂贵”的。在可行的情况下,我们希望避免此操作。如果您知道数组的最大可能大小,请在声明数组时指定该大小。整个连续的内存块会被立即占用,在数组中保留空内容,直到稍后将指针分配给元素。
在数组中插入中间位置也很昂贵。要么建立一个新数组并复制元素,要么必须将插入点后的所有元素向下移动到其相邻位置。 这些操作都不会影响数组中的对象。 对象漂浮在内存的虚空中,对数组一无所知。对数组的操作不会影响对象或它们在内存中的位置。唯一的关系是,如果数组中持有的引用是仍指向该对象的最后一个引用,则当清除或删除该数组元素时,该对象将成为垃圾收集的候选对象。

基本类型

在Java中,八种基本类型(byteshortintlongfloatdoublebooleanchar)不是对象/类也不是面向对象编程。其中一个优点是它们比对象快且占用的内存较少。

一个原始数据类型数组保存的是数组内部的值。因此这些值在内存中是紧密相连的,没有引用/指针,也没有在内存中跳来跳去。
至于添加或插入操作,与上面讨论的行为相同。只不过,现在被洗牌的不再是指针,而是实际的原始值。

enter image description here

提示

在商业应用程序中,通常最好使用对象。

这意味着使用包装类而不是基本类型。例如,使用Integer代替int。Java中的auto-boxing功能使这更容易,自动在原始值和它们的对象包装之间进行转换。

而且,首选对象意味着使用Collection而不是数组,通常使用List,特别是ArrayList。或者对于不可变使用,可以使用从新的List.of方法返回的List实现。

与商业应用相反,在极端情况下,例如游戏引擎,速度和内存使用量至关重要,则充分利用数组和基本类型。

如果Valhalla项目中的工作取得成果,那么将来对象和基本类型之间的区别可能会变得模糊。


优美的解释。 - the_prole
Java在存储引用而非对象时如何处理缓存未命中问题?在这种情况下,缓存只能用于指针。从数组中获取指针后,它必须进入主内存而不是CPU缓存。我想知道Java是否解决了这个问题。 - Ali Berat Çetin
如果您的问题涉及通过指针访问的状态的一致性,则仅在跨线程时才会出现问题。解决方案之一是使用AtomicReference类或使用关键字volatile。有关此问题的强制阅读资料:Brian Goetz等人的Java并发实践 - Basil Bourque
@BasilBourque 不是这样的。我的意思是,如果Java数组由指针组成,我们想要访问的真正对象在内存中是以任意顺序存在的。因此,这会导致巨大的缓存未命中率。 - Ali Berat Çetin
面向对象编程并不是为了最大化性能而设计的。如果您想要在小数据集(例如整数或浮点数集合)中获得最大性能,则应使用原始值数组而不是对象。一个 int[]float[] 可以适应 CPU 缓存。但是,如果您真的关心管理缓存未命中,也许您应该使用诸如 C、Rust 或 Swift 等 系统编程语言,而不是 Java。 - Basil Bourque

0
Java仅处理对对象的引用。因此,无法保证数组元素在内存中是连续的。
编辑:看起来我的回答并不是很清楚,抱歉。我的意思是,即使引用是连续的(因为1-D数组是连续存储的),也不能保证对象本身是连续的。不过,Basil Bourque的答案完美地解释了这个问题。

0
数据或值存储在对象中,使用对象的引用来检索值。再解释一下,在Java中,数组以对象的形式存储。因此,毫无疑问,对象存储值并使用该特定对象的引用变量进行访问。希望你明白了。

如果我理解正确的话,这意味着数组的所有元素实际上将在内存中连续吗? 我得到了另一个答案告诉我它们不会是连续的... 我感到困惑。 - Joza100
@Joza100 如果它是引用类型的数组,也就是对象,那么数组会存储指针。指针将是连续的,而实际的对象则可能分散在其他地方。 - hyde

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接