【问题标题】:How to efficiently add multiple new objects to an ArrayList如何有效地将多个新对象添加到 ArrayList
【发布时间】:2020-11-10 03:58:27
【问题描述】:

我有一个类 ArrayList MyClass。我想将新元素添加到 ArrayList 中。目前,我正在使用 for 循环来声明新变量 myElement。在每个循环中,然后添加到 ArrayList 的末尾。

但是看起来效率很低,尤其是myArrayLength很大的时候。这也花费了我大量的计算时间。有没有一种方法可以在不使用 for 循环的情况下向 MyClass 添加多个新的 MySubClass 实例?谢谢。

public class MyClass<K,V> extends ArrayList<MyClass.MySubClass> {

    MyClass(int myArrayLength) {

        super(myArrayLength);
    
        for (int i = 0; i < myArrayLength; i++) {
            MySubClass myElement = new MySubClass();
            this.add(myElement);            
        }
    }

    protected class MySubClass {
    
    }

}

【问题讨论】:

  • 在哪些方面效率低下?如果myArrayLength 是 1000000,那么创建 100 万个对象将需要一些时间,而且它的完成速度不会比该代码更快。 --- 你如何期望在不使用循环的情况下创建 100 万个对象?
  • 您可以使用多线程,这将缩短所需的时间,但我怀疑时间增益会抵消实现(和调试)所需的努力。

标签: java performance arraylist instance inner-classes


【解决方案1】:

使用延迟包装类对数组元素进行延迟初始化怎么样。见

https://www.infoworld.com/article/2077568/java-tip-67--lazy-instantiation.amp.html

https://docs.microsoft.com/en-us/dotnet/framework/performance/lazy-initialization 这将避免创建许多对象的前期成本。

您也可以考虑对象池。见

https://docs.microsoft.com/en-us/dotnet/standard/collections/thread-safe/how-to-create-an-object-pool

但请注意: https://softwareengineering.stackexchange.com/questions/115163/is-object-pooling-a-deprecated-technique

我们可能会简单地询问您为什么要预先分配这么多实例,以至于您不得不担心时间。分析是否表明您在应用程序的这方面有很大的性能损失?

【讨论】:

    【解决方案2】:

    要填充列表,您必须重复做两件事:

    • 实例化一个新对象
    • 将该新对象添加到列表中

    您的for 循环就是这样做的。没有比这更有效的方法了。

    实例化需要时间,需要找到一块空闲内存,分配该内存,填充该内存,并返回该内存的地址。

    将每个对象添加到列表中需要一点时间,但时间非常短暂。请记住,ArrayList 由数组支持。因此,该列表的插槽的内存已经分配。当您调用ArrayList::add 时,您只是将对象引用(指针、内存地址)写入现有的空白槽中。

    唯一的性能损失是如果您添加的元素多于后备数组中的当前大小和可用空间。如果连续内存可用,则必须扩大数组,否则会创建一个新的更大的数组并复制元素。您可以通过将ArrayList 的初始容量设置为预期元素的数量来消除该次要影响。您可以通过将一个数字传递给构造函数来设置初始容量。如果您高估了元素的数量,请调用 ArrayList::trimToSize 以释放仍然为空的插槽使用的内存。

    在不同的主题上,不要在没有充分理由的情况下继承 ArrayList。您的效率问题不值得采取这一举措。 ArrayList 类应按原样使用。

        int limit = 52_000 ;
        ArrayList< MyClass > list = new ArrayList<>( limit );
    
        for ( int i = 0 ; i < limit ; i++ ) {
            MyClass myElement = new MyClass() ;
            list.add( myElement ) ;            
        }
        // If any slots may not have been used, drop them to free up memory.
        list.trimToSize() ;
    

    您可以将循环内的两行压缩为一行。

        list.add( new MyClass() ) ;   
    

    但这种浓缩不太可能影响性能。无论如何,HotSpot 或 OpenJ9 等优化编译器很可能会这样,所以我不会为压缩而烦恼。我更喜欢短的分隔线,以便于跟踪和记录。

    【讨论】:

      【解决方案3】:

      很可能,您向我们展示的代码来自某个更大的项目,被剥离到核心。我们只能评论我们所看到的,这可能与您的整体情况不符。说了这么多,我继续。

      首先,在谈到性能时,您应该为自己准备一个不错的分析器工具,并找出您的程序在哪里消耗了它的时间。我猜,这将是 new MySubClass() 表达式,而不是 list.add(myElement);,但这是猜测,也可能是完全错误的。

      当使用 new MyClass(1000000) 之类的东西调用时,您当前的代码会构造一个包含一百万个元素的列表,并用一百万个单独的 MySubClass 实例填充它。这些实例都是相同的,没有任何变化,都来自相同的无参数new MySubClass() 表达式。

      我非常怀疑包含 100 万个相同元素的列表是否是您真正需要的。我猜,您以后想在列表中存储更多有意义的元素,而初始元素只是作为“到目前为止没有任何有意义的分配”的占位符。然后,您可以为此发明一个更便宜的占位符,例如null.

      【讨论】:

        猜你喜欢
        • 2013-10-10
        • 2021-06-20
        • 1970-01-01
        • 2023-03-22
        • 1970-01-01
        • 2021-01-28
        • 2021-05-26
        • 2019-02-18
        相关资源
        最近更新 更多