Удаление объектов в Java ArrayList-потребление времени

Я пытаюсь удалить 140,000 объектов из ArrayList размера 7,140,000. Я ожидал, что это займет секунды (если это), но вместо этого Java занимает несколько секунд на тысячу объектов. Вот мой код:

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

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

         list.remove(index);
     }

Примечание: P-константа, которую я ранее установил в 7,000,000.

целью цикла является случайное удаление объектов из списка до тех пор, пока его размер не составит 7,000,000.

Java занимает так много времени, потому что я начинаю с более чем 7 миллионами объектов? Я никогда не замечал этой проблемы эффективности при удалении из ArrayLists в прошлом. Если это поможет,я использую IDE Drjava Beta.

2 ответов


ArrayList поддерживается массивом, поэтому модификации должны действительно перемещать элементы в сторону, а в некоторых случаях даже создавать целый новый массив.

некоторые возможные решения:

  1. вместо этого используйте реализацию LinkedList или skip-list. Обратите внимание, что здесь для удаления элемента по-прежнему требуется O(N) (или O(logN) в skip-list), потому что он должен найти его. Однако вы можете пересечь элементы с шансом, основанным на том, сколько элементов у вас есть удаленный.

  2. вы можете случайным образом принимать элементы из входных данных в новый ArrayList, пока не получите нужное количество элементов. Вы должны знать, какие элементы вы добавили, хотя, так пересекать линейным способом, и иметь случайный выбор, чтобы иметь шанс, сколько шагов, чтобы пойти, на основе того, сколько элементов вы переместили.

  3. самое простое решение: перетасуйте весь входной массив, а затем выберите первые M элементов.

здесь возможный код решения #3:

public static List<String> pickNRandom(List<String> lst, int m) {
    Collections.shuffle(lst);
    return lst.subList(0, n);
}

недостатком здесь является то, что он разрушает порядок элементов. Вы можете преодолеть это, создав копию списка в качестве входных, но это займет больше памяти (временно) ...


каждый раз, когда вы удаляете элемент из ArrayList, он должен перетасовать все элементы с большими индексами вниз на один слот. Скажем, вы удаляете первый элемент списка 7M-элементов - вам также нужно переместить 6,999,999 элементов.

если вы делаете это в цикле, это займет O(n^2), где n - размер списка. Для списка 7M-элементов это будет довольно медленно.

вместо этого, если вы знаете, какие элементы вы хотите удалить в заранее, вы можете переместить все элементы вниз в один проход:

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();

здесь toRemove(src) является некоторой функцией, которая говорит, Хотите ли вы удалить src-го элемента.

например, вы можете построить BitSet все, кроме P элементы набора:

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);
}

вам все равно придется переместить все элементы 6,999,999 вправо, если вы просто удалите нулевой элемент из списка элементов 7M; но любые другие удаления не требуют больше сдвигов верхний. Этот алгоритм O(n), где N-размер списка.


Edit: вы можете выбрать P элементы из списка (где P <= list.size()) такой:

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();

эта стратегия будет выбирать элементы из списка с равной вероятностью ( * ) и хорошо работает для любого значения P; он также сохраняет исходный порядок.


если вы хотите попробовать K элементы из списка с помощью N элементы без рисования того же элемента дважды, есть choose(N, K) = N! / (K! * (N-K)!) способы сделать это. Если вы хотите выбрать все элементы из списка с равной вероятностью, то вы должны выбрать любую из этих c(n,k) различные конфигурации.

когда есть k элементы слева, чтобы выбрать из n элементы, вы будете либо:

  • выберите первый пункт; а затем выберите k-1 элементы из оставшихся n-1 предметы; или
  • не выбрать первый элемент; а затем выберите k элементы от остальных n-1 предметы.

для того, чтобы обеспечить равную вероятность выбора K элементы, общее, вам нужно выбрать один из двух вариантов в зависимости от количества комбинаций для подбора от n-1 элементы:

                                   #(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

Итак, когда у вас есть k элементы слева, чтобы взять из n, вы должны взять первый пункт k/n времени.

два интересных случая, которые следует отметить:

  • когда k == n, k/n = 1, поэтому всегда берем элемент. Интуитивно, если вам нужно выбрать n из n, вы должны взять их всех.
  • , когда k == 0, k/n = 0, поэтому никогда берем элемент. Интуитивно, если вы уже выбрали все K ваших деталей,вам не нужно принять больше.

чтобы реализовать это, вы можете просто сгенерировать равномерно распределенное случайное число r в диапазоне [0..n), и "взять" элемент из списка, если r < k.

с точки зрения реализации выше,k = P - dst и n = list.size() - src.