提问人:ayush 提问时间:6/24/2023 更新时间:7/3/2023 访问量:211
Array 和 ArrayList 之间的区别<>在 Java 中内存分配方面?
Difference between Array and ArrayList<> in Java in terms of memory allocation?
问:
我看到一篇文章,里面有一个问题——
连续内存位置通常用于将实际值存储在数组中,但不用于存储在 ArrayList 中。解释。
上面帖子中的以下几行造成了一些混乱——
数组的元素存储在连续的内存位置中,这意味着每个元素都存储在基于它位于数组内的单独块中。由于数组的元素存储在连续位置,因此通过其索引访问任何元素相对容易,因为元素地址可以根据元素的位置计算。但是 Java 将 ArrayLists 实现为动态数组,这意味着大小可以随着元素的删除或添加而改变。ArrayList 元素不存储在连续的内存位置中,以适应这种动态特性。
public static void main(String[] args) {
int primitiveArray[]=new int[5];
Integer objectArray[]=new Integer[5];
ArrayList<Integer> list=new ArrayList<>(5);
for(int i=0;i<5;i++){
primitiveArray[i]=i;
objectArray[i]=i;
list.add(i);
}
}
现在,我理解的是,当我创建原始数组时,元素存储在连续的内存位置。当我创建一个 Integer 数组时,对象是在堆上创建的(可能不在连续内存位置),并且引用存储在连续内存位置。当我创建一个 ArrayList 时,它在内部使用一个 Object[] 数组,并将对象的引用(在堆上创建,可能不是连续的)存储在连续的内存位置。
那么,什么是对的?我从文章中引用的文字或我给出的解释(我在这里找到 - https://www.geeksforgeeks.org/internal-working-of-arraylist-in-java/)?请帮我理解这个概念!
答:
你的理解是对的。在 Java 数组中存储基元类型时,它们保存在连续的内存分配中。每个元素都直接保存基元类型的值,例如整数、浮点数或布尔值。
另一方面,在创建对象或整数数组时,它本质上是一个引用数组。这些引用也存储在连续的内存分配中,这意味着它们一个接一个地放置在内存中。但是,引用本身指向内存中存储实际对象或整数值的不同位置。
例如,考虑一个 Objects 数组。数组中的每个元素都是对存储在内存中其他位置的 Object 的引用。引用本身按顺序存储在数组中,但它们可以指向分散在整个内存中的对象。
ArrayList 实现将项存储在 Object[] elementData 中,这意味着项不会保存在连续的内存分配中。
查看源代码以获取更多信息 https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/ArrayList.java
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
* @throws OutOfMemoryError if minCapacity is less than zero
*/
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
评论
“我看到一篇文章,里面有一个问题......
...上面帖子中的以下行会造成一些混乱......”
基元数组中的数据将连续存储,因为它的内容是文本值序列。
然而,对象数组大小需要计算。
请看以下示例。
这是可以计算的,因为数据是静态的;3、32位值。
int[] values = { 1, 2, 3 };
然而,这最初只是连续的。由于在初始化后对值进行调整将对堆中的对象重新排序。
void example() {
Example[] values = new Example[3];
}
class Example {
int value;
void setValue(int value) {
this.value = value;
}
}
"...现在,我理解的是,当我创建原始数组时,元素存储在连续的内存位置。..."
正确;连续,表示顺序。
"...当我创建一个 Integer 数组时,对象是在堆上创建的(可能不在连续内存位置),它们的引用存储在连续内存位置。..."
因此,堆没有任何类型的连续内存,它只是 JVM 使用的分配和查找表。
随着数据的发展,对象将移动到堆中的不同位置。
另一方面,堆栈是一个队列,因此默认情况下,每个元素都是连续的。
"...当我创建一个 ArrayList 时,它在内部使用一个 Object[] 数组,并将对象的引用(在堆上创建,可能不是连续的)存储在连续的内存位置。..."
不。对象的地址,在 ArrayList 中,可能会出现在堆中的任何位置,因此它们不会是连续的。
例如,假设您创建了一个 ArrayList 并向其添加 3 个对象。
您可以假设它们很可能一个接一个地在堆中,因此,默认情况下它们是连续的。
现在,想象一下您的代码继续并为其他杂项对象分配各种堆内存。
然后,再向 ArrayList 添加 3 个对象。
这 3 个新对象不会附加到 ArrayList 中的初始 3 个对象中,它们将简单地放置在堆中,并添加到 JVM 的查找表中。
"...那么,什么是对的?...请帮我理解这个概念!
我建议研究堆栈和堆内存分配的差异。
您正在考虑的概念与这两种类型的存储原则有关。
评论
你的理解是正确的(据我所知)。
因此,我将重点关注您从 Geeks For Geeks 面试问题页面引用的文本:
数组的元素存储在连续的内存位置,
这是正确的,但不完整。对于基元类型的数组,基元值存储在连续位置。但对于引用类型的数组,它是存储在连续内存位置中的元素引用。
...这意味着每个元素都存储在一个单独的块中,该块基于它位于数组中。
这是对连续位置的错误(或乱码)定义。(它甚至不是语法英语!连续位置实际上意味着这些位置是一个接一个的记忆......没有间隙。例如,在 中,字节将存储在内存中,它们之间没有间隙,对于其他原始数组和对象数组也是如此。byte[]
换句话说,这是英语单词“contiguous”的正常含义。
由于数组的元素存储在连续位置,因此通过其索引访问任何元素相对容易,因为元素地址可以根据元素的位置计算。
这句话是对的。但它也适用于s!ArrayList
但是 Java 将 ArrayLists 实现为动态数组,这意味着大小可以随着元素的删除或添加而改变。
这是误导性的。An 是 ,列表通常表现为动态数组。但是 是用一个支持数组实现的,该数组是一个真正的 Java 对象数组。而且该阵列是固定大小的......像所有 Java 数组一样。“诀窍”是,如果在将元素添加到列表中时发现现有数组太小,则实现将创建一个新的后备数组。ArrayList
List
ArrayList
ArrayList
ArrayList 元素不存储在连续的内存位置中,以适应这种动态特性。
这是不正确的。
事实上,元素><保存在连续内存中,就像对象数组元素是连续的一样(见上文!元素引用保存在后备数组中的连续内存位置中,但它们将引用堆中保存的对象。这些对象几乎总是位于非连续的内存位置。ArrayList
“动态性质”是通过分配新的支持阵列和复制元素来适应的。(您可以查看源代码以查看它是如何完成的。ArrayList
是的,即使您创建了一个原始数组,该数组也只会存储在具有连续内存分配的堆中。
在 Java 中,对象仅在堆上创建。因此,每当您使用 new 运算符时,您都可以确保对象将存储在堆中。例如,int primitiveArray[] = new int[5];
现在的问题是 ArrayList 是否使用连续内存分配。在我看来,ArrayList 还具有连续内存分配,因为 ArrayList 的底层实现是数组本身,而数组使用连续内存分配。
根据 Java 文档:
除了添加元素具有恒定的摊销时间成本这一事实之外,没有指定增长策略的细节。
使用 ArrayList 时,对象的引用将存储在连续内存分配中,但引用可能不是连续的。
正如您已经说过的,这在您的问题中:
当我创建一个 ArrayList 时,它在内部使用一个 Object[] 数组,并将对象的引用(在堆上创建,可能不是连续的)存储在连续的内存位置。
这是正确的。
评论