ArrayList:大小如何增加?


75

我对Java有一个基本问题ArrayList

ArrayList被声明和初始化使用默认构造,对于10个元件的存储器空间被创建。现在,当我添加第11个元素时,会发生什么?是否将创建具有20个(或更多)元素容量的新内存空间(这需要将元素从第一个内存位置复制到新位置)或其他东西?

在这里检查。但是我没有找到答案。

请分享知识。谢谢。


1
找出答案的最佳方法是实际阅读源代码。但是要当心。这是龙。
darioo 2010年

1
是OpenJDK 6的ArrayList的来源。请注意,它有很多实现(GNU Classpath,Apache Harmony,OpenJDK等),它们可能有所不同。
Bart Kiers 2010年

大多数实现将增长1.5倍:octoperf.com/blog/2018/03/19/java-arraylist
Jerome L

Answers:


52

创建一个新数组,并将旧数组的内容复制过来。这就是您在API级别上所知道的。引用文档(我的重点):

每个ArrayList实例都有一个容量。容量是用于在列表中存储元素的数组的大小。它总是至少与列表大小一样大。将元素添加到ArrayList后,其容量会自动增长。除了添加元素具有固定的摊销时间成本外,没有指定增长策略的详细信息。

关于特定实现ArrayList(例如Sun的实现)实际发生的方式,在这种情况下,您可以在源代码中查看细节。但是当然,依靠特定实现的细节通常不是一个好主意...


1
将创建一个新数组,并将旧数组的内容复制过来。那么旧数组会在内存中还是删除后会发生什么呢?
维卡斯2015年

1
@VikasKumarSingh:旧的对象有资格进行垃圾回收,就像任何不再有引用它的对象一样。何时(或什至)发生取决于JVM。
TJ Crowder 2015年

从中复制元素的旧ArrayList会发生什么情况?GC会回收该内存吗?
Ravi.Kumar

2
@ Ravi.Kumar:没有旧的ArrayList,只有旧的数组。是的,ArrayList释放了对旧数组的引用,这是对旧数组的唯一引用,这意味着它可以使用GC。
TJ Crowder

1
@Denson-每当JVM无法分配内存时,它要么就死掉(或者试图分配它的速度太慢以至于它也可能已经死了),或者它成功抛出了OutOfMemoryError。从我几年前的经验来看,我似乎通常会设法抛出错误(为此目的预留了一些内存),但是使用YMMV。
TJ Crowder

36

Sun的JDK6:

我相信它会增加到15个元素。不进行编码,而是查看jdk中的grow()代码。

然后int newCapacity = 10 +(10 >> 1)= 15。

/**
 * 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
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

从Javadoc来看,它说这是从Java 2开始的,因此在Sun JDK中可以肯定。

编辑:对于那些没有得到乘数1.5int newCapacity = oldCapacity + (oldCapacity >> 1);

>>是将数字减少一半的右移运算符。因此,
int newCapacity = oldCapacity + (oldCapacity >> 1);
=> int newCapacity = oldCapacity + 0.5*oldCapacity;
=>int newCapacity = 1.5*oldCapacity ;


1
是的,阅读源代码是了解行为的最佳方法。我也阅读了代码并得到了相同的答案。jdk 8就是这样
。– ZhaoGang


11

当我们尝试将对象添加到arraylist时,

Java检查以确保现有阵列中有足够的容量来容纳新对象。如果不是,则创建一个更大的新数组,使用Arrays.copyOf旧数组复制到新数组,并将新数组分配给现有数组。

查看下面的代码(摘自GrepCode.com上的Java ArrayList代码)。

检查这个例子

在此处输入图片说明

编辑:

public ArrayList()构造一个初始容量为10的空列表。

public ArrayList(int initialCapacity)我们可以指定初始容量。

public ArrayList(Collection c)构造一个列表,该列表包含指定集合的​​元素,这些元素的顺序由集合的迭代器返回。

现在,当我们使用ArrayList()构造函数时,我们将得到一个Size = 10的ArrayList 。在列表中添加第11个元素时,将在sureCapacity()方法内创建新的Arraylist。

使用以下公式:

  int newCapacity= (oldCapacity * 3)/2 +1;

6
该运动:)当您在两年后的Google搜索中找到答案时
VdeX '16

8

直到JDK 6,容量随着公式的增长而增加newCapacity = (oldCapacity * 3/2) + 1

在JDK 7及更高版本中,公式更改为newCapacity = oldCapacity + (oldCapacity >> 1)

所以,如果初始容量为10当时的新能力将16 in JDK615 in above JDK7


7

当使用默认构造函数声明和初始化ArrayList时,将创建10个元素的内存空间。现在,当我添加第11个元素时,会发生什么

ArrayList创建一个具有以下大小的新对象

即OldCapacity * 3/2 + 1 = 10 * 3/2 + 1 = 16


6

让我们看一下这段代码(jdk1.8)

@Test
    public void testArraySize() throws Exception {
        List<String> list = new ArrayList<>();
        list.add("ds");
        list.add("cx");
        list.add("cx");
        list.add("ww");
        list.add("ds");
        list.add("cx");
        list.add("cx");
        list.add("ww");
        list.add("ds");
        list.add("cx");
        list.add("last");
    }

1)插入“ last”时将断点放在行上

2)转到添加方法,ArrayList 您将看到

ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;

3)转到ensureCapacityInternal方法这个方法调用 ensureExplicitCapacity

4)

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
            return true;

在我们的示例中,minCapacity等于11,11-10 > 0因此您需要grow方法

5)

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

让我们描述每个步骤:

1)oldCapacity= 10,因为我们在ArrayList初始化时未放置此参数,因此它将使用默认容量(10)

2)int newCapacity = oldCapacity + (oldCapacity >> 1); 这里newCapacity等于oldCapacity加上oldCapacity并右移一位(oldCapacity is 10这是二进制表示形式00001010,向右移动一位,我们将得到00000101十进制的5,因此newCapacity10 + 5 = 15

3)

if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;

例如你的初始化容量是1,当添加所述第二元件到的ArrayListnewCapacity将等于1(oldCapacity) + 0 (moved to right by one bit) = 1 在此情况下newCapacity小于minCapacity和elementData(内部的ArrayList阵列对象)不能保持新的元件,因此newCapacity等于minCapacity

4)

if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);

检查数组大小是否达到MAX_ARRAY_SIZE(即Integer.MAX-8)。为什么ArrayList的最大数组大小为Integer.MAX_VALUE-8?

5)最后将旧值复制到长度为15的newArray中


5

通常,将ArrayList类型的容器的内存增加一倍。因此,如果最初有10个项目的空间并添加了10个项目,则第11个项目将添加到20个项目的新(内部)数组中。这样做的原因是,如果每次内部数组已满时将大小加倍,则以固定大小为单位将数组增加到一个很好的O(n),则添加项的增量成本将从O(n ^ 2)减少。


3

上下文Java 8

我在Oracle Java 8实现的上下文中给出答案,因为在阅读所有答案之后,我发现gmgmiller给出了Java 6上下文中的答案,而Java 7给出了另一个答案。但是尚未给出Java 8如何实现大小增加的信息。

在Java 8中,大小增加行为与Java 6相同,请参见growArrayList的方法:

   private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

关键代码是这一行:

int newCapacity = oldCapacity + (oldCapacity >> 1);

显然,增长因子也为1.5,与Java 6相同。


3
对于无法获得“ 1.5”推导的人。将oldCapacity视为x,则newCapacity = x + x /(2 ^ 1)= x + x / 2 = 1.5x
vsriram92

1
运算符'>>'会在移位时进行符号扩展,对于某些运算符可能会有些混乱,因为由于不使用负值,因此它不被视为div运算符。在这种情况下,由于长度始终> = 0,它才起作用。作为div运算符,应主要使用'>>>'。
JAAAY

2

使用默认构造函数声明和初始化ArrayList时,将创建10个元素的内存空间。

没有。初始化ArrayList后,将为一个空数组分配内存。仅在将第一个元素添加到ArrayList后才为默认容量(10)进行内存分配。

 /**
 * 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 == EMPTY_ELEMENTDATA will be expanded to
 * DEFAULT_CAPACITY when the first element is added.
 */
private transient Object[] elementData;

PS没有足够的声誉来评论问题,因此我将其作为答案,因为之前没有人指出这个错误的假设。


1

在以下情况下,ArrayList确实会增加负载因子的大小:

  • 初始容量: 10
  • 负载系数: 1(即列表满时)
  • 增长率: current_size + current_size / 2

上下文:JDK 7

在向中添加元素时ArrayListpublic ensureCapacityInternal内部会发生以下调用和其他私有方法调用,以增加大小。这就是动态增加的大小的原因ArrayList。在查看代码时,您可以通过命名约定来理解逻辑,因此,我没有添加明确的描述

public boolean add(E paramE) {
        ensureCapacityInternal(this.size + 1);
        this.elementData[(this.size++)] = paramE;
        return true;
    }

private void ensureCapacityInternal(int paramInt) {
        if (this.elementData == EMPTY_ELEMENTDATA)
            paramInt = Math.max(10, paramInt);
        ensureExplicitCapacity(paramInt);
    }
private void ensureExplicitCapacity(int paramInt) {
        this.modCount += 1;
        if (paramInt - this.elementData.length <= 0)
            return;
        grow(paramInt);
    }

private void grow(int paramInt) {
    int i = this.elementData.length;
    int j = i + (i >> 1);
    if (j - paramInt < 0)
        j = paramInt;
    if (j - 2147483639 > 0)
        j = hugeCapacity(paramInt);
    this.elementData = Arrays.copyOf(this.elementData, j);
}

1

从JDK源代码中,我发现以下代码

int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);

0

发生的情况是使用n * 2的空间创建了一个新数组,然后将旧数组中的所有项目复制过来,并将新项目插入第一个可用空间。总而言之,这导致ArrayList的添加时间为O(N)。

如果您使用的是Eclipse,请安装JadJadclipse来反编译库中保存的JAR。我这样做是为了阅读原始源代码。



0

ArrayList的默认容量为10。一旦容量达到最大容量,ArrayList的大小将为16,一旦容量达到最大容量16,ArrayList的大小将为25,并根据数据大小继续增加。 ...

怎么样?这是答案和公式

New capacity = (Current Capacity * 3/2) + 1

因此,如果默认容量为10,则

Current Capacity = 10
New capacity = (10 * 3/2) + 1
Output is 16

0
static int getCapacity(ArrayList<?> list) throws Exception {
            Field dataField = ArrayList.class.getDeclaredField("elementData");
            dataField.setAccessible(true);
            return ((Object[]) dataField.get(list)).length;
    }

修改arraylist时,使用上述方法检查大小。


0

在Jdk 1.6中:新容量=(当前容量* 3/2)+ 1;

在Jdk 1.7中:

int j = i +(i >> 1); 这与新容量=(当前容量* 1/2)+当前容量相同;

例如:大小会增加:10-> 15-> 22-> 33


0

(oldSize * 3)/ 2 + 1

如果您使用的是默认构造函数,则初始大小为,ArrayList则可以10在创建的对象时传递数组的初始大小ArrayList

示例:如果是默认构造函数

List<String> list = new ArrayList<>();
list.size()

示例:如果参数化构造函数

List<String> list = new ArrayList<>(5);
list.size()

-3

arraylist的默认大小为10。当我们添加第11个.... arraylist时,大小会增加(n * 2)。存储在旧arraylist中的值将复制到大小为20的新arraylist中。


这并没有添加2010
。– Shaun the Sheep 2012年

当您添加11元的容量将是15不是20
马南沙阿
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.