Удалить элементы из CopyOnWriteArrayList

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

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

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

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

Ответ 1

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

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

Ответ 2

EDIT: Я идиот. Я пропустил тот факт, что это список для копирования на запись, поэтому каждое удаление означает новую копию. Таким образом, мои предложения ниже, вероятно, будут субоптимальными, если есть несколько удалений.

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

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

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

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

Ответ 3

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

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

EDIT: Ну, конечно, это работает, если вы хотите удалить элементы по ссылке, а не по положению.

Ответ 4

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

Ответ 5

Что-то вроде этого:

int pos = 0;
while(pos < lst.size() ) {
  Foo foo = lst.get(pos);
  if( hasToBeRemoved(foo) ) {
    lst.remove(pos);
    // do not move position
  } else {
    pos++;
  }
}

Ответ 6

Вместо списка можно использовать Queue.

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

Он потокобезопасен и поддерживает iterator.remove(). Помните о потокобезопасном поведении итераторов очереди, хотя (проверьте javadoc).

Ответ 7

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

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

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

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