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


93

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

令人惊讶的是,对默认(无效)构造函数的评论仍然是: 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;
}

Answers:


105

从技术上讲,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保证的容量,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


4
根据bugs.java.com/bugdatabase/view_bug.do?bug_id=7143928的介绍,它可以减少堆的使用并缩短响应时间(显示了两个应用的数量)
ThomasKläger15年

3
@khelwood:除了通过此Javadoc之外,ArrayList并没有真正“报告”其容量:没有getCapacity()方法或类似的方法。(也就是说,ensureCapacity(7)对于默认初始化的ArrayList来说,它是无操作的,所以我想我们真的应该以其初始容量为真正的10。。。)
ruakh 2015年

10
很好的挖掘。默认的初始容量确实不是零,而是10,默认情况是作为特殊情况延迟分配的。如果您反复向ArrayList使用no-arg构造函数创建的元素添加元素,而不是向构造函数传递零int,并且您以反射方式或在调试器中查看内部数组大小,则可以观察到这一点。在默认情况下,数组以1.5倍的增长率从长度0跳到10,然后跳到15、22。初始容量超过零会导致从0增长到1、2、3、4、6、9、13、19 ...
Stuart Marks 2015年

13
我是变更和引用的电子邮件的作者Mike Duigou,我批准此消息。🙂正如Stuart所说,其动机主要是节省空间,而不是性能,但由于经常避免创建后备阵列,因此还有一点性能优势。
Mike Duigou 2015年

4
@assylias:; ^)不,它仍然占有一席之地,因为单例emptyList()仍然比几个空ArrayList实例消耗更少的内存。现在它的重要性不再那么重要,因此并不是每个地方需要它,尤其是不是在以后添加元素可能性更高的地方。还请记住,有时您需要一个不可变的空列表,然后emptyList()才可以使用。
Holger 2015年

23

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

现在的问题是,为什么要在JAVA 8中进行此更改?

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

延迟初始化将这种内存消耗推迟到您实际使用数组列表的那一刻。

请参见下面的代码以获取帮助。

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

本文详细介绍了Java 8中ArrayList的默认容量


7

如果使用ArrayList进行的第一个操作是传递addAll包含十个以上元素的集合,那么创建初始十个元素的数组来保存ArrayList内容的所有工作将被排除在窗口之外。每当将任何内容添加到ArrayList时,都必须测试结果列表的大小是否会超过后备存储的大小;允许初始后备存储的大小为零而不是十,将导致该测试在列表的生命周期内失败一次,该列表的第一个操作是“添加”,这将需要创建初始的十项数组,但是成本是少于创建十项数组的成本,这种数组永远不会被使用。

话虽如此,如果存在“ addAll”的重载,该重载指定了在当前项之后可能会向列表中添加多少项(如果有)的话,在某些情况下可能有可能进一步提高性能。使用它来影响其分配行为。在某些情况下,将最后几个项目添加到列表中的代码将具有一个很好的主意,即列表将永远不需要任何空间。在许多情况下,列表只会填充一次,此后再也不会修改。如果此时代码知道列表的最终大小为170个元素,则它具有150个元素和大小为160的后备存储,


关于很好的要点addAll()。这是提高第一个malloc效率的又一个机会。
kevinarpe 2015年

@kevinarpe:我希望Java的库能够以更多方式设计程序,以指示程序可能会如何使用。例如,子字符串的旧样式在某些用例中很糟糕,而在另一些用例中却很出色。如果有单独的函数“可能要比原始版本持久的子字符串”和“不太可能比原始版本持久的子字符串”,并且代码在90%的时间内使用了正确的代码,我认为那些代码可能会大大优于旧的或新的字符串实现。
supercat

3

问题是“为什么?”。

内存配置文件检查(例如(https://www.yourkit.com/docs/java/help/inspections_mem.jsp#sparse_arrays))显示空数组(填充有null)占用大量内存。

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

引入的修改消除了^ W将该内存消耗推迟到实际使用数组列表的那一刻。


请用“浪费”更正“消费”。您提供的链接并不意味着它们到处都在吞噬内存,只是具有null元素的数组浪费了分配给它们的内存,这是不成比例的。“消费”意味着他们神奇地使用了超出其分配范围的内存,事实并非如此。
mechalynx 2015年

0

JAVA 8中ArrayList的默认大小为stil10。JAVA8中所做的唯一更改是,如果编码器添加的元素少于10个,则剩余的arraylist空白位置不会指定为null。之所以这样说,是因为我自己经历了这种情况,并且日食使我着眼于Java 8的这种变化。

您可以通过查看以下屏幕截图来证明此更改的合理性。在其中,您可以看到在Object [10]中将ArrayList的大小指定为10,但是显示的元素数仅为7。其余的空值元素不在此处显示。在JAVA 7中,下面的屏幕快照与只做一个更改是相同的,即还显示了null值元素,如果编码员正在迭代完整的数组列表,则编码器需要为其编写代码以处理null值,而在JAVA 8中,此负担被消除了。编码/开发负责人。

屏幕截图链接。


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.