删除Java ArrayList中的对象-时间消耗

删除Java ArrayList中的对象-时间消耗,java,performance,arraylist,Java,Performance,Arraylist,我正在尝试从大小为7140000的ArrayList中删除140000个对象。我原以为这需要几秒钟(如果是这样的话),但实际上Java每1000个对象需要几秒钟。这是我的密码: for (int i = list.size(); i > P; i--) { int size = list.size(); int index = (int) (Math.random() * size); list.remove(i

我正在尝试从大小为7140000的ArrayList中删除140000个对象。我原以为这需要几秒钟(如果是这样的话),但实际上Java每1000个对象需要几秒钟。这是我的密码:

     for (int i = list.size(); i > P; i--)
     {
         int size = list.size();

         int index = (int) (Math.random() * size);

         list.remove(index);
     }
注意:p是一个常数,我以前设置为7000000

循环的目标是从列表中随机删除对象,直到其大小为7000000


Java花了这么长的时间,是因为我刚开始使用700多万个对象吗?在过去,我从未注意到从ArrayList中删除的效率问题。如果有帮助的话,我会使用DrJava Beta IDE。

每次从ArrayList中删除一个元素时,它都必须将索引较大的所有元素向下移动一个插槽。假设您删除了7M元素列表中的第一个元素,那么您还必须移动6999999个元素

如果在循环中执行此操作,则需要
O(n^2)
时间,其中
n
是列表的大小。对于700万个元素列表,这将是相当缓慢的

相反,如果您事先知道要删除哪些图元,则可以在一次过程中向下移动所有图元:

int dst = 0;
for (int src = 0; src < list.size(); ++src) {
  if (!toRemove(src)) {
    list.set(dst++, list.get(src));
  }
}
list.subList(dst, list.size()).clear();
如果只是从元素列表中删除零元素,则仍然必须将所有6999999元素向右移动;但任何其他的移除都不需要在顶部进行更多的转换。这个算法是
O(n)
,其中n是列表的大小



编辑:您可以从列表中选择
p
元素(其中
p数组列表由数组支持,因此修改时需要真正将项目移到一边,在某些情况下甚至创建一个全新的数组

一些可能的解决办法:

  • 请考虑改用LinkedList或skip list实现。请注意,在此处,要删除项目,仍然需要O(N)(或skip list中的O(logN)),因为它必须找到该项目。但是,您可以根据删除的项目数量,有机会遍历项目

  • 您可以将输入中的项目随机添加到新的ArrayList中,直到获得所需的项目数为止。您必须知道添加了哪些项目,因此以线性方式遍历,并让随机选择器根据移动的项目数确定要执行的步数

  • 最简单的解决方案:洗牌整个输入数组,然后选择前M个项目

  • 下面是解决方案#3的可能代码:

    公共静态列表pickNRandom(列表lst,int m){
    收藏。洗牌(lst);
    返回第一个子列表(0,n);
    }
    

    这里的缺点是它会破坏项目的顺序。您可以通过创建列表副本作为输入来克服这一问题,但这会占用更多内存(暂时)…

    我同意。
    ArrayList
    实际上是一个数组。只有当您不打算删除它时,才可以使用它,只添加元素。如果您想从中删除元素,最好创建另一个
    ArrayList
    并只添加必需的元素。作为替代方法,您可以使用
    LinkedList
    ,这更有用当您在中间修改列表时(添加或删除其中的元素)。谢谢你的回答。我刚才做了一些我自己的研究,这与你回答中的想法相吻合。我不知道每次传递时将所有元素向下移动需要多少时间,但这是有意义的。我的解决方案是随机将140000个元素设置为null,然后将所有元素设置为“非null”临时ArrayList中的元素。然后我将list设置为临时ArrayList。我认为这与您的解决方案相同,并且它运行得很快。@oleg.cherednik ArrayList可以在一般情况下使用。如果它很小或者几乎不需要任何操作。在这里它太多了。…@oleg.cherednik是的,我最终完成了您提到的操作。它是很好。@InertialIgnorance如果您愿意,我已经写了一些其他可能的解决方案。剩余项目的顺序重要吗?如果唯一的标准是列出大小为7000000的列表,为什么您需要随机执行此操作并支付移动数组的费用?为什么您不能将元素从7140000删除到7000000?好主意……我从不这么想解决方案的ht#3.非常优雅。是否有内置Java函数可用于洗牌ArrayList?或者我必须编写自己的函数。此外,洗牌ArrayList中的所有元素是否会导致与我相同的问题(许多元素不断“向下移动一个”)?@InertialIgnorance实际上有一个函数:,但我从未使用过它。它很容易实现。您可以在列表的每个项上使用循环,并与列表中的随机项交换。我认为解决方案3是最好的,因为您可以在最后创建新列表,以及结果项。唯一可能的问题是你破坏了输入项的顺序。你可以暂时保存顺序,但这会占用额外的空间…下面是一个示例答案,我发现它类似于解决方案#3:还有其他解决方案,类似于我写的解决方案#1,#2:很有意义。在列表中运行一个循环,并在每次迭代中交换两个随机对象应该非常有用在我的例子中,顺序根本不重要,所以解决方案是理想的。
    BitSet toRemove = new BitSet(list.size());
    for (int i = list.size(); i > P; i--) {
      int rand;
      do {
        rand = Math.random() * list.size();
      } while (toRemove.get(rand));
      toRemove.set(rand, true);
    }
    
    int dst = 0;
    Random rand = new Random();
    for (int src = 0; dst < P; ++src) {
      if (rand.nextInt(list.size() - src) < (P-dst)) {
        list.set(dst++, list.get(src));
      }
    }
    list.subList(dst, list.size()).clear();
    
                                       #(combinations after taking first item) 
    P(take first item) = ------------------------------------------------------------------
                         #(combinations after taking) + #(combinations after not taking)
    
                       = C(n-1,k-1) / (C(n-1, k-1) + C(n-1, k))
    
                       = ... working omitted ...
    
                       = k / n
    
    public static List<String> pickNRandom(List<String> lst, int m) {
        Collections.shuffle(lst);
        return lst.subList(0, n);
    }