当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《在Java8中,为什么ArrayList的默认容量现在为零?》经验,为你挑选了3个好方法。

我记得,在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;
}

Lukas Eder.. 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

我是Mike Duigou,变更的作者和引用的电子邮件,我批准了这条消息.正如斯图尔特所说,动机主要是节省空间而不是性能,但由于经常避免创建支持阵列,因此也有轻微的性能优势. (12认同)

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

根据http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7143928,它可以减少堆使用并缩短响应时间(显示两个应用程序的数量) (4认同)

@assylias:; ^)不,它仍然有单位`emptyList()`仍然比几个空的`ArrayList`实例消耗更少的内存.它现在变得不那么重要了,因此在每个*地方都不需要,特别是在以后添加元素的可能性更高的地方.还要记住,你有时*想要*一个不可变的空列表,然后`emptyList()`是要走的路. (4认同)

@khelwood:除了通过这个Javadoc之外,ArrayList并没有真正"报告"它的容量:没有`getCapacity()`方法,或类似的东西.(也就是说,像`ensureCapacity(7)`之类的东西对于默认初始化的ArrayList是无操作的,所以我想我们真的应该表现得好像它的初始容量真的是10 ...... (3认同)

@Stuart Marks:我们可以反过来看到它.在我们的应用程序中,我们*使用`Collections.emptyList()`来关注和初始化很多`List`变量,并在我们要实际添加暗示有大量条件代码检查的元素时将它替换为`ArrayList`任何添加元素之前的空列表.使用新的`ArrayList`实现,我们可以使用`ArrayList`并摆脱我们的条件代码. (3认同)

@Eugene在此更改之前,`new ArrayList <>()`和`new ArrayList <>(0)`将具有不同的增长顺序。添加了空的前哨阵列,以便可以在保留生长顺序的同时进行延迟分配。 (2认同)


Shailesh Vik.. 18

在java 8中,ArrayList的默认容量为0,直到我们将至少一个对象添加到ArrayList对象中(您可以将其称为延迟初始化).请参阅以下代码以获取帮助.

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中完成了这个改变?

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

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

文章Java 8中ArrayList的默认容量详细解释了它.



1> Lukas Eder..:

从技术上讲,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


我是Mike Duigou,变更的作者和引用的电子邮件,我批准了这条消息.正如斯图尔特所说,动机主要是节省空间而不是性能,但由于经常避免创建支持阵列,因此也有轻微的性能优势.
好挖.默认初始容量确实不是零,而是10,默认情况下是作为特殊情况延迟分配的.如果你反复向使用no-arg构造函数创建的`ArrayList`添加元素vs将零传递给`int`构造函数,并且反过来或在调试器中查看内部数组大小,则可以观察到这一点.在默认情况下,数组从长度0跳到10,然后在1.5倍增长率后跳到15,22.通过零作为初始容量导致从0增加到1,2,3,4,6,9,13,19 ....
根据http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7143928,它可以减少堆使用并缩短响应时间(显示两个应用程序的数量)
@assylias:; ^)不,它仍然有单位`emptyList()`仍然比几个空的`ArrayList`实例消耗更少的内存.它现在变得不那么重要了,因此在每个*地方都不需要,特别是在以后添加元素的可能性更高的地方.还要记住,你有时*想要*一个不可变的空列表,然后`emptyList()`是要走的路.
@khelwood:除了通过这个Javadoc之外,ArrayList并没有真正"报告"它的容量:没有`getCapacity()`方法,或类似的东西.(也就是说,像`ensureCapacity(7)`之类的东西对于默认初始化的ArrayList是无操作的,所以我想我们真的应该表现得好像它的初始容量真的是10 ......
@Stuart Marks:我们可以反过来看到它.在我们的应用程序中,我们*使用`Collections.emptyList()`来关注和初始化很多`List`变量,并在我们要实际添加暗示有大量条件代码检查的元素时将它替换为`ArrayList`任何添加元素之前的空列表.使用新的`ArrayList`实现,我们可以使用`ArrayList`并摆脱我们的条件代码.
@Eugene在此更改之前,`new ArrayList <>()`和`new ArrayList <>(0)`将具有不同的增长顺序。添加了空的前哨阵列,以便可以在保留生长顺序的同时进行延迟分配。

2> Shailesh Vik..:

在java 8中,ArrayList的默认容量为0,直到我们将至少一个对象添加到ArrayList对象中(您可以将其称为延迟初始化).请参阅以下代码以获取帮助.

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中完成了这个改变?

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

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

文章Java 8中ArrayList的默认容量详细解释了它.



3> supercat..:

如果使用ArrayList完成的第一个操作是传递addAll一个包含十个以上元素的集合,那么创建一个初始十元素数组以保存ArrayList内容的任何工作都将被抛出窗口.每当有东西被添加到ArrayList时,都需要测试结果列表的大小是否超过后备存储的大小; 允许初始后备存储的大小为零而不是10将导致此测试在列表的生命周期中失去一个额外的时间,该列表的第一个操作是"添加",这将需要创建初始的十项数组,但该成本是低于创建一个永远不会被使用的十项数组的成本.

话虽如此,如果有一个"addAll"超载指定在当前列表之后可能会将多少项(如果有的话)添加到列表中,那么在某些情况下可能会进一步提高性能.用它来影响它的分配行为.在某些情况下,将最后几个项添加到列表中的代码将非常清楚该列表永远不需要除此之外的任何空间.在许多情况下,列表将填充一次,之后从未修改过.如果在点代码知道列表的最终大小将是170个元素,它有150个元素和大小为160的后备存储,将后备存储增长到320大小将是无益的,并将其保留为320或修改为170的效率低于简单地将下一个分配增加到170.

推荐阅读
放ch养奶牛
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有