Java: итерация по набору при изменении содержимого набора

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

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


person nomel7    schedule 19.06.2012    source источник
comment
То, как вы предлагаете, кажется прекрасным.   -  person assylias    schedule 19.06.2012
comment
Чтобы уточнить - это однопоточный или многопоточный?   -  person templatetypedef    schedule 19.06.2012
comment
Многопоточный. Один поток выполняет итерацию, другой поток изменяет набор. Я не хочу приостанавливать ни один из потоков из-за проблем с производительностью.   -  person nomel7    schedule 19.06.2012


Ответы (6)


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

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

Это единственные наборы, которые я вижу в java.util.concurrent. , что является естественным пакетом для таких коллекций. Снять копию, наверное, все же проще :)

person Jon Skeet    schedule 19.06.2012
comment
Это зависит. Если моментальный снимок не требуется (никто не вставляет его во время итерации), CopyOnWriteArraySet будет работать быстрее. Так что это зависит от того, как часто происходят реальные столкновения. - person user949300; 20.06.2012
comment
@ user949300: Я не знаю подробностей, когда CopyOnWriteArraySet действительно требует создания копии, но в документации утверждается, что обычно (что бы это ни значило). Конечно, было бы неплохо, если бы он копировался только тогда, когда это действительно необходимо... - person Jon Skeet; 20.06.2012

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

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

Например:

Set<T> masterSet = /* ... */

Set<T> newElems = /* ... */
for (T obj: masterSet) {
     /* ... do something to each object ... */
}

masterSet.addAll(newElems);

Надеюсь это поможет!

person templatetypedef    schedule 19.06.2012
comment
Мне нравится этот метод, потому что (а) он создает меньше временных объектов, чем копирование всего исходного набора, и (б) он позволяет избежать больших накладных расходов на параллелизм, которые вам могут не понадобиться. I can't see any guarantees around behaviour of an iterator in terms of seeing new elements может быть проблемой для ConcurrentSkipListSet - person Stephen P; 19.06.2012
comment
Я не уверен, как это может работать. Откуда второй поток знает, что он должен добавлять в newElems, а не в masterSet??? И, если вы НЕ итерируете, кто знает, как затем объединить newElems в masterSet ??? - person user949300; 19.06.2012
comment
@ user949300- Я бы предположил, что любой код, который добавляет элементы в набор, может знать, что такое новый набор элементов. Также обратите внимание, что вопрос OP ничего не говорит о многопоточности; Я думаю, что проблема была в комодификации, а не в параллелизме. Было бы очень легко передать эту новую информацию другим потокам, если бы они существовали. - person templatetypedef; 19.06.2012
comment
@templatetypedef - я понял, что его вопрос означает многопоточность, но во втором чтении он мог иметь в виду коммодификацию, и в этом случае ваше решение действительно идеально. Я также вижу ваш комментарий к ОП с просьбой предоставить дополнительную информацию. - person user949300; 19.06.2012
comment
Поскольку ОП пояснил, что он говорит о многопоточности, этот ответ больше не подходит. (по крайней мере, не без дополнительной работы) - person user949300; 20.06.2012
comment
@ user949300- Эту стратегию можно заставить работать. Вам просто нужно сделать небольшую дополнительную синхронизацию. Хотя я согласен, что есть гораздо лучшие подходы, учитывая, что это предназначено для многопоточности. - person templatetypedef; 20.06.2012

Создание копии Set является элегантным решением.

Set<Obj> copyOfObjs = new HashSet<Obj>(originalSet);
for(Obj original : originalSet) {
    //add some more stuff to copyOfObjs
}
person nicholas.hauschild    schedule 19.06.2012

Вы можете использовать ConcurrentHashMap. с фиктивными ключами. Или ConcurrentSkipListSet

person Suraj Chandran    schedule 19.06.2012

Как уже предлагали другие, оптимального решения для того, что вы ищете, не существует. Все зависит от варианта использования вашего приложения или использования множества.
Поскольку Set — это интерфейс, вы можете определить свой собственный класс DoubleSet, который будет реализовывать Set и, скажем, будет использовать два поля HashSet.
Когда вы извлекаете итератор, вы должны пометить один из этих наборов как «режим только взаимодействия», чтобы метод add добавлял только к другому набору


Я все еще новичок в Stackoverlflow, поэтому мне нужно понять, как вставлять код в мои ответы :( но в целом у вас должен быть класс с именем MySet (Generic универсального типа T), реализующий Set универсального типа T.
Вам нужно реализовать все методы , и иметь два поля: одно называется iterationSet, а другое — insertionSet.
У вас также будет логическое поле, указывающее, вставлять ли в два набора или нет. Когда вызывается метод iterator(), это логическое значение должно быть установлено значение false, что означает, что вы должны вставлять только в набор вставки.
У вас должен быть метод, который будет синхронизировать содержимое двух наборов после того, как вы закончите с итератором.
Надеюсь, я ясно выразился.

person Yair Zaslavsky    schedule 19.06.2012

Теперь, когда OP уточнил требования, решения

  1. Скопируйте набор перед итерацией
  2. Используйте CopyOnWriteArraySet.
  3. Напишите свой собственный код и постарайтесь быть умнее многих умных людей.

Недостаток № 1 заключается в том, что вы всегда копируете набор, даже если он может не понадобиться (например, если во время итерации на самом деле не происходит вставок). Я бы предложил вариант № 2, если вы не докажете, что частые вставки вызывают реальную производительность проблема.

person user949300    schedule 19.06.2012