在Java 8中,为什么ArrayList的默认容量现在为零?

时间:2015-12-13 10:41:13

标签: java arraylist java-8

我记得,在Java 8之前,ArrayList的默认容量是10。

令人惊讶的是,对default(void)构造函数的注释仍然说:Constructs an empty list with an initial capacity of ten.

来自ArrayList.java

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

...

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

6 个答案:

答案 0 :(得分:102)

从技术上讲,它是10,而不是零,如果您承认后备阵列的延迟初始化。参见:

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

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

,其中

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

您所指的只是在所有最初为空的ArrayList个对象之间共享的零大小的初始数组对象。即10的容量保证 lazily ,这是Java 7中也存在的一种优化。

不可否认,构造函数合同并不完全准确。也许这就是混乱的根源。

背景

这是Mike Duigou的电子邮件

  

我发布了空ArrayList和HashMap补丁的更新版本。

     

http://cr.openjdk.java.net/~mduigou/JDK-7143928/1/webrev/

     

此修订后的实施向任一类引入了无新字段。对于ArrayList,仅当列表以默认大小创建时,才会发生后备阵列的延迟分配。根据我们的性能分析团队,大约85%的ArrayList实例是以默认大小创建的,因此这种优化对于绝大多数情况都是有效的。

     

对于HashMap,创建使用阈值字段来跟踪请求的初始大小,直到需要存储桶阵列。在读取端,使用isEmpty()测试空映射大小写。在写入大小上,(table == EMPTY_TABLE)的比较用于检测是否需要给桶阵列充气。在readObject中,尝试选择有效的初始容量需要做更多的工作。

     

来自:http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-April/015585.html

答案 1 :(得分:13)

在java 8中,ArrayList的默认容量为0,直到我们将至少一个对象添加到ArrayList对象中(您可以将其称为延迟初始化)。

现在问题是为什么在JAVA 8中进行了这种改变?

答案是节省内存消耗。在实时Java应用程序中创建了数百万个数组列表对象。默认大小为10个对象意味着我们在创建时为底层数组分配10个指针(40或80个字节),并用空值填充它们。 空数组(填充空值)占用大量内存。

延迟初始化会推迟此内存消耗,直到您实际使用数组列表为止。

请参阅以下代码以获取帮助。

ArrayList al = new ArrayList();          //Size:  0, Capacity:  0
ArrayList al = new ArrayList(5);         //Size:  0, Capacity:  5
ArrayList al = new ArrayList(new ArrayList(5)); //Size:  0, Capacity:  0
al.add( "shailesh" );                    //Size:  1, Capacity: 10

public static void main( String[] args )
        throws Exception
    {
        ArrayList al = new ArrayList();
        getCapacity( al );
        al.add( "shailesh" );
        getCapacity( al );
    }

    static void getCapacity( ArrayList<?> l )
        throws Exception
    {
        Field dataField = ArrayList.class.getDeclaredField( "elementData" );
        dataField.setAccessible( true );
        System.out.format( "Size: %2d, Capacity: %2d%n", l.size(), ( (Object[]) dataField.get( l ) ).length );
}

Response: - 
Size:  0, Capacity:  0
Size:  1, Capacity: 10

文章Default capacity of ArrayList in Java 8详细解释。

答案 2 :(得分:7)

如果是与一个ArrayList进行的非常第一操作是通过addAll,其具有大于10组的元素,然后放入创建初始10个元素的数组来保存该ArrayList&#39的任何努力的集合; s内容将被抛出窗外。无论什么时候添加到ArrayList,都需要测试结果列表的大小是否超过后备存储的大小;允许初始后备存储的大小为零而不是10将导致此测试在列表的生命周期中失去一个额外的时间,该列表的第一个操作是&#34;添加&#34;这需要创建初始的十项数组,但是这个成本低于创建一个永远不会被使用的十项数组的成本。

有人说过,如果有超负荷的话,可能会在某些情况下进一步提高性能#add; AllAll&#34;它指定了在当前列表之后可能会将多少项(如果有)添加到列表中,并且可以使用它来影响其分配行为。在某些情况下,将最后几个项添加到列表中的代码将非常清楚该列表永远不需要除此之外的任何空间。在许多情况下,列表将填充一次,之后从未修改过。如果在点代码都知道,一个列表的最终大小将是170层的元件,其具有150层的元件和尺寸160的后备存储器,生长所述后备存储到尺寸320将是无益的,在尺寸为320离开或将其修剪170的效率低于简单地将下一个分配增加到170.

答案 3 :(得分:3)

问题是'为什么?'。

内存分析检查(例如(https://www.yourkit.com/docs/java/help/inspections_mem.jsp#sparse_arrays)显示空(填充空值)数组占用大量内存。

默认大小为10个对象意味着我们在创建时为底层数组分配10个指针(40或80个字节),并用空值填充它们。真正的Java应用程序创建了数百万个数组列表。

引入的修改删除^ W推迟了这个内存消耗,直到您实际使用数组列表为止。

答案 4 :(得分:0)

在上述问题之后,我浏览了Java 8的ArrayList Document。我发现默认大小仍然是10。

Please see below

答案 5 :(得分:0)

JAVA 8中的ArrayList默认大小是stil 10.在JAVA 8中唯一的变化是,如果编码器添加小于10的元素,则剩余的arraylist空白位置不指定为null。这么说是因为我自己经历了这种情况并且日食让我看到了JAVA 8的这种变化。

您可以通过查看以下屏幕截图来证明此更改的合理性。在其中,您可以看到在Object [10]中将ArrayList大小指定为10,但显示的元素数量仅为7.此处不显示其余的null值元素。在下面的JAVA 7中,屏幕截图与单个更改相同,即如果他在JAVA 8中迭代完整的数组列表,则编码器还需要编写用于处理空值的代码的空值元素。编码员/开发人员。

Screen shot link.