【问题标题】:Java ArrayList add item outside current sizeJava ArrayList 添加当前大小之外的项目
【发布时间】:2019-05-21 00:09:07
【问题描述】:

想知道是否有一种有效的方法可以将项目添加到 Java 的 ArrayList 中,使其位置大于其当前大小:

场景:

   ArrayList<Item> items = new ArrayList<Item>;
   ... let's say I add three elements

现在我想在位置 10 添加一个项目(将项目从 3 到 10 保留为 null)

  items.add(10,newItem);  // item.size() == 3 

有没有一种有效的方法来调整/填充 ArrayList 的大小?

Java 的实现将 size 字段设为私有 :-(..

【问题讨论】:

  • 你应该使用 HashMap 或 SortedMap 来代替。
  • 好吧,使用 Map 不是内存问题的解决方案,我们知道最后结构已满 -> TIntObjectHashMap (trove) 可能
  • HashMap 不支持排序。 SortedMap 是更好的选择。
  • 内存是什么原因?为什么你会假设 Map 使用的内存比列表多得多?
  • 一种方法是拨打add(null) 8 次。

标签: java arraylist


【解决方案1】:

恕我直言,你能做的最好的事情是 items.addAll(Collections.nCopies(6, null)) 并希望 ArrayList 实现一些行为以在内部固定它

【讨论】:

  • 这很好,但从性能的角度来看有点吓人
  • 好吧,nCopies 只会为数组生成一个列表包装器,这样您的 ArrayList 就可以使用 System.arraycopy 来填充空值,具体取决于它的实现程度
  • public boolean addAll(Collection extends E> c) { Object[] a = c.toArray(); int numNew = a.length;确保容量(大小 + numNew); // 递增 modCount System.arraycopy(a, 0, elementData, size, numNew);大小 += numNew;返回 numNew != 0; }
  • 瞧,但你的解决方案是迄今为止最好的方案之一 :-)
  • 那么,与多次调用 add(null) 相比(如果我们假设数字没有修复),这“从性能的角度来看”到底可怕在哪里?
【解决方案2】:

这个怎么样?

ArrayList<Item> items = new ArrayList<Item>();

items.add(new Item(0));
items.add(new Item(1));
items.add(new Item(2));

items.addAll(Collections.<Item>nCopies(7, null));
items.add(10,new Item(10));

System.out.println(items);

打印

[0, 1, 2, null, null, null, null, null, null, null, 10]

【讨论】:

  • 使用10 - items.size 而不是7 作为更强大的选项。
【解决方案3】:

这是一个较老的问题,但您现在可以使用SparseArray 作为ArrayList 的(几乎)直接替代品。它允许不连续的整数键值,如果没有为键设置值,则返回 null。在性能方面,它的设计完全满足您的需求。您使用append 而不是add,它更具描述性,因为您将附加到最大键的末尾,即使有间隙也是如此。您也可以 set 使用您想要的任何键值,即使它超出了最大键值。

【讨论】:

    【解决方案4】:

    改为使用 TreeMap。这是检查内存消耗的简单示例。分别运行第一个和第二个测试并使用 jvisualvm 检查堆大小。记得多次执行 GC。

        public class Test {
    
    
                public static void main(String[] args) throws InterruptedException {
                    String s = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque metus.";
    
    
                    //Test 1
                    ArrayList<String> l = new ArrayList<String>();
    
                    for (int i = 0; i < 1000000; i++) {
                        l.add(s + " " + i);
                        l.addAll(Collections.nCopies(i % 10, (String)null)); //Add some nulls
                    }
                    //Heap is > 5MB
    
                    //Test 2 uncomment and comment test 1
        //          SortedMap<Integer, String> map = new TreeMap<Integer, String>();
        //          for (int i = 0; i < 1000000; i++) {
        //              map.put(i,s + " " + i);
        //          }
                    //Heap is < 5MB
    
                    Thread.sleep(100000);
    
                }
        }
    

    看起来TreeMap 版本比 ArrayList 版本消耗的内存更少。检查自己。

    【讨论】:

    • Peter,ArrayList 是一个数组,仅此而已。一个完整的数组怎么可能比任何其他结构都占用更多的大小? ...我认为您的示例有问题(我确定)
    • 数组是 (s - string, n- null): TreeMap 没有这个问题。正如你所说,它是一个数组。即使是 1mio 空数组仍然需要内存。关键在于您的清单有多“密集”。
    • 如果是一个完整的数组,问题出在哪里?使用Collection.nCopies 方法放置一些空值。
    • 我想你误解了这个问题,一旦填充完成,就没有空值了。检查大小更像是:
    • for (int i = 0; i
    【解决方案5】:

    不,你不能:

    http://docs.oracle.com/javase/6/docs/api/java/util/ArrayList.html#add(int, E)

    抛出:IndexOutOfBoundsException - 如果索引超出范围 (index size())

    【讨论】:

    【解决方案6】:

    不,你不能这样做, 但是,如果您想这样做,请在剩余索引中添加空对象,例如..

        ArrayList<Object> items = new ArrayList<Object>();
        items.add(new Object());
        items.add(new Object());
        items.add(new Object());
        items.add(3,new Object());
    

    【讨论】:

    • 其实我很幸运能得到一些更优雅的东西:-)
    【解决方案7】:

    我会考虑在这里使用SortedMap 而不是列表。这将允许索引不存在:

    SorteMap<Integer, Item> myMap = new TreeMap<Integer, Map>();
    int i=0;
    myMap.put(i++, first);
    myMap.put(i++, second);
    myMap.put(i++, third);
    myMap.put(10, other);
    

    如您所说,如果地图真的不起作用。然后我建议围绕 ArrayList 创建一个装饰器。在插入方法中,添加空值以填充空位置。我建议使用 Guava 的ForwardingList 来简化课程的创建。这样,您只需实现一种方法。

    【讨论】:

    • 带有 1mio 对象的 TreeMap 很大。
    • ArrayList 中间有 1mio 个对象和空值,要小得多?
    • ArrayList主要是满的。
    【解决方案8】:

    如果内存和索引如此重要,请使用普通数组。

    当它变得很小时,使用System.arraycopy 这就是 ArrayList 在内部执行它的方式。

    --

    即使您使用 ArrayList 并拥有一百万个对象,建议使用 ArrayList(int initialCapacity)-Constructor 以避免大量复制操作

    【讨论】:

      【解决方案9】:

      @icCube- 你说,该列表应该已满 90%。 我对这个解决方案的想法是:

      • 如果您确切知道目标大小 - 使用普通数组
      • 如果您知道目标大小 - 使用初始容量尽可能接近目标大小的 ArrayList。正如人们所说,将空值与l.addAll(Collections.nCopies(n, (String)null)); 放在一起。
      • 如果您不知道目标大小 - 您的 ArrayList 将多次调整大小。调整大小意味着复制整个底层数组(它使用 Arrays.copyOf)。你可以想象如果数组被复制会发生什么 - GC 有很多工作。然后使用 TreeMap。

      【讨论】:

        【解决方案10】:

        使用构造函数ArrayList(int initialCapacity)。这样您就可以设置初始容量。

        【讨论】:

        • InitialCapacity 不是大小 -> 线程“main”java.lang.IndexOutOfBoundsException 中的异常:索引:9,大小:0
        • 尝试: public static void main(String[] args) { ArrayList list = new ArrayList(10); list.add(9,3); }
        猜你喜欢
        • 2014-09-07
        • 1970-01-01
        • 1970-01-01
        • 2017-02-10
        • 1970-01-01
        • 2017-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多