Удаление объектов в 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 поддерживается массивом, поэтому модификации должны действительно перемещать элементы в сторону, а в некоторых случаях даже создавать целый новый массив.
некоторые возможные решения:
вместо этого используйте реализацию LinkedList или skip-list. Обратите внимание, что здесь для удаления элемента по-прежнему требуется O(N) (или O(logN) в skip-list), потому что он должен найти его. Однако вы можете пересечь элементы с шансом, основанным на том, сколько элементов у вас есть удаленный.
вы можете случайным образом принимать элементы из входных данных в новый ArrayList, пока не получите нужное количество элементов. Вы должны знать, какие элементы вы добавили, хотя, так пересекать линейным способом, и иметь случайный выбор, чтобы иметь шанс, сколько шагов, чтобы пойти, на основе того, сколько элементов вы переместили.
самое простое решение: перетасуйте весь входной массив, а затем выберите первые 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
.