Array 和 ArrayList 之间的区别<>在 Java 中内存分配方面?

Difference between Array and ArrayList<> in Java in terms of memory allocation?

提问人:ayush 提问时间:6/24/2023 更新时间:7/3/2023 访问量:211

问:

我看到一篇文章,里面有一个问题——

连续内存位置通常用于将实际值存储在数组中,但不用于存储在 ArrayList 中。解释。

https://www.geeksforgeeks.org/java-interview-questions/#:~:text=Contiguous%20memory%20locations%20are%20usually%20used%20for%20storing%20actual%20values%20in%20an%20array%20but%20not%20in%20ArrayList.%20Explain

上面帖子中的以下几行造成了一些混乱——

数组的元素存储在连续的内存位置中,这意味着每个元素都存储在基于它位于数组内的单独块中。由于数组的元素存储在连续位置,因此通过其索引访问任何元素相对容易,因为元素地址可以根据元素的位置计算。但是 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 数组 对象 arraylist 堆内存

评论

1赞 Stephen C 6/24/2023
“那么,什么是对的?”- 你所理解的是正确的。极客对极客的面试问题页面充满了不准确之处......更不用说糟糕的英语了。

答:

2赞 Borislav Gizdov 6/24/2023 #1

你的理解是对的。在 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)];
        }
    }

评论

0赞 ayush 6/24/2023
是的,我看到了源代码,这让我产生了怀疑。那么,我所说的解释是对的吗?
0赞 Borislav Gizdov 6/24/2023
我改变了我的答案,其实,你的解释是对的。
-2赞 Reilas 6/24/2023 #2

“我看到一篇文章,里面有一个问题......

...上面帖子中的以下行会造成一些混乱......”

基元数组中的数据将连续存储,因为它的内容是文本值序列。
然而,对象数组大小需要计算。

请看以下示例。

这是可以计算的,因为数据是静态的;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 的查找表中。

"...那么,什么是对的?...请帮我理解这个概念!

我建议研究堆内存分配的差异。
您正在考虑的概念与这两种类型的存储原则有关。

评论

0赞 Stephen C 6/24/2023
Java 中的原始数组不存储在堆栈上。(您可能会将 Java 与 C 和 C++ 混淆......
0赞 Reilas 6/24/2023
@StephenC,这正是我所经历的。你是说因为 Java 将原始数组视为对象?
0赞 Stephen C 6/24/2023
是的。这就是我要说的。基元数组>是<对象。(Java 中的所有数组类型都是引用类型。
0赞 ayush 6/24/2023
数组不存储在堆栈 stackoverflow.com/questions/2099695/...
0赞 Reilas 6/24/2023
好了,编辑了。
4赞 Stephen C 6/24/2023 #3

你的理解是正确的(据我所知)。

因此,我将重点关注您从 Geeks For Geeks 面试问题页面引用的文本:

数组的元素存储在连续的内存位置,

这是正确的,但不完整。对于基元类型的数组,基元值存储在连续位置。但对于引用类型的数组,它是存储在连续内存位置中的元素引用

...这意味着每个元素都存储在一个单独的块中,该块基于它位于数组中。

这是对连续位置的错误(或乱码)定义。(它甚至不是语法英语!连续位置实际上意味着这些位置是一个接一个的记忆......没有间隙。例如,在 中,字节将存储在内存中,它们之间没有间隙,对于其他原始数组和对象数组也是如此。byte[]

换句话说,这是英语单词“contiguous”的正常含义。

由于数组的元素存储在连续位置,因此通过其索引访问任何元素相对容易,因为元素地址可以根据元素的位置计算。

这句话是对的。但它也适用于s!ArrayList

但是 Java 将 ArrayLists 实现为动态数组,这意味着大小可以随着元素的删除或添加而改变。

这是误导性的。An 是 ,列表通常表现为动态数组。但是 是用一个支持数组实现的,该数组是一个真正的 Java 对象数组。而且该阵列是固定大小的......像所有 Java 数组一样。“诀窍”是,如果在将元素添加到列表中时发现现有数组太小,则实现将创建一个新的后备数组。ArrayListListArrayListArrayList

ArrayList 元素不存储在连续的内存位置中,以适应这种动态特性。

这是不正确的。

事实上,元素><保存在连续内存中,就像对象数组元素是连续的一样(见上文!元素引用保存在后备数组中的连续内存位置中,但它们将引用堆中保存的对象。这些对象几乎总是位于非连续的内存位置。ArrayList

“动态性质”是通过分配新的支持阵列和复制元素来适应的。(您可以查看源代码以查看它是如何完成的。ArrayList

1赞 Sachin Mewar 6/25/2023 #4

是的,即使您创建了一个原始数组,该数组也只会存储在具有连续内存分配的堆中。

在 Java 中,对象仅在堆上创建。因此,每当您使用 new 运算符时,您都可以确保对象将存储在堆中。例如,int primitiveArray[] = new int[5];

现在的问题是 ArrayList 是否使用连续内存分配。在我看来,ArrayList 还具有连续内存分配,因为 ArrayList 的底层实现是数组本身,而数组使用连续内存分配。

根据 Java 文档:

除了添加元素具有恒定的摊销时间成本这一事实之外,没有指定增长策略的细节

使用 ArrayList 时,对象的引用将存储在连续内存分配中,但引用可能不是连续的。

正如您已经说过的,这在您的问题中:

当我创建一个 ArrayList 时,它在内部使用一个 Object[] 数组,并将对象的引用(在堆上创建,可能不是连续的)存储在连续的内存位置。

这是正确的。