удалить элементы из CopyOnWriteArrayList - PullRequest
13 голосов
/ 10 апреля 2011

Я получаю исключение при попытке удалить элементы из CopyOnWriteArrayList с помощью итератора.Я заметил, что это задокументировано

Операции смены элементов на самих итераторах (удаление, установка и добавление) не поддерживаются.Эти методы выдают UnsupportedOperationException.

(из http://download.oracle.com/javase/6/docs/api/java/util/concurrent/CopyOnWriteArrayList.html)

Теперь, к удивлению, я могу повторить его с помощью foreach и использовать функцию remove (). Но затем я получаю известную ошибку -при попытке удалить элемент из списка с помощью цикла for - вы пропускаете элемент рядом с удаленным элементом. какие-либо предложения тогда?

Ответы [ 8 ]

21 голосов
/ 10 апреля 2011

Переберите коллекцию, выбрав все элементы, которые вы хотите удалить, и поместите их во временную коллекцию. После завершения итерации удалите все найденные элементы из исходной коллекции, используя метод removeAll.

Это сработает для вас? Я имею в виду, не уверен, что логика удаления более сложна, чем в вашем алгоритме.

7 голосов
/ 10 апреля 2011

РЕДАКТИРОВАТЬ: Я идиот. Я упустил тот факт, что это список копирования при записи, поэтому каждое удаление означает новую копию . Так что мои предложения ниже, вероятно, будут неоптимальными, если существует более одного удаления.

То же, что и для любого другого списка, чей итератор не поддерживает удаление или что-либо, где вы не используете итератор. Чтобы избежать этой ошибки, на ум приходит три основных метода:

  1. Уменьшите индекс после удаления чего-либо (будьте осторожны, чтобы ничего не делать с индексом до следующей итерации). Для этого вам, очевидно, придется использовать цикл for в стиле for(int i=0; i < ..., чтобы вы могли манипулировать индексом.

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

  3. Перебирать список в обратном порядке (от конца к началу, а не от начала к концу). Я предпочитаю этот подход, так как он самый простой.

4 голосов
/ 24 ноября 2016

Так как это CopyOnWriteArrayList, абсолютно безопасно удалять элементы при итерации с forEach.Нет необходимости в причудливых алгоритмах.

list.forEach(e -> {
    if (shouldRemove(e))
        list.remove(e);
});

РЕДАКТИРОВАТЬ: Конечно, это работает, если вы хотите удалить элементы по ссылке, а не по положению.

2 голосов
/ 10 апреля 2011

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

1 голос
/ 12 июня 2014

Вы можете использовать Очередь вместо списка.

private Queue<Something> queue = new ConcurrentLinkedQueue<Something>();

Это потокобезопасный и поддерживает iterator.remove(). Однако помните о поточно-ориентированном поведении итераторов очереди (проверьте javadoc).

1 голос
/ 10 апреля 2011

Примерно так:

int pos = 0;
while(pos < lst.size() ) {
  Foo foo = lst.get(pos);
  if( hasToBeRemoved(foo) ) {
    lst.remove(pos);
    // do not move position
  } else {
    pos++;
  }
}
0 голосов
/ 04 января 2019

Ниже отлично работает с CopyOnWriteArrayList

for(String key : list) {
    if (<some condition>) {
        list.remove(key);
    }
}
0 голосов
/ 11 января 2018

самый короткий и самый эффективный способ:

List<String> list = new CopyOnWriteArrayList<>();
list.removeIf(s -> s.length() < 1);

внутренне создает временный массив такой же длины и копирует все элементы, где предикат возвращает true.

имейте в виду, что еслиэтот метод используется для фактической итерации по элементам для выполнения какого-либо действия; эти действия больше нельзя выполнять в параллельном режиме, поскольку вызов removeIf является атомарным и блокирует обход для других потоков

...