Мало того, что процессоры x86 способны считывать и записывать один байт, на это способны все современные процессоры общего назначения. Что еще более важно, большинство современных процессоров (включая x86, ARM, MIPS, PowerPC и SPARC) способны атомарно считывать и записывать отдельные байты.
Я не уверен, о чем говорил Страуструп. Раньше было несколько машин с адресацией слов, которые не были способны к 8-битной адресации байтов, например Cray, и, как сказал Питер Кордес, ранние процессоры Alpha не поддерживали загрузку и хранение байтов, но сегодня единственные процессоры, неспособные к байту, сегодня. нагрузки и хранилища - это определенные DSP, используемые в нишевых приложениях. Даже если предположить, что он имеет в виду, что большинство современных процессоров не имеют атомарной байтовой нагрузки и сохраняют данные, это не относится к большинству процессоров.
Тем не менее, простые атомарные загрузки и хранилища не очень полезны в многопоточном программировании. Вам также обычно нужны гарантии упорядочения и способ сделать операции чтения-изменения-записи атомарными. Другое соображение заключается в том, что, хотя процессор a может иметь инструкции загрузки и хранения байтов, компилятору не требуется их использовать. Например, компилятор все еще может генерировать код, описанный Страуструпом, загружая как b
, так и c
, используя инструкцию загрузки одного слова в качестве оптимизации.
Поэтому, хотя вам нужна четко определенная модель памяти, если только так компилятор вынужден генерировать код, который вы ожидаете, проблема не в том, что современные процессоры не способны загружать или хранить что-то меньшее, чем слово.
Как насчет создания очереди с элементами, которые вы хотите перебирать; когда вы хотите добавить элементы, ставьте их в очередь в конце очереди и продолжайте удалять элементы, пока очередь не станет пустой. Вот как обычно работает поиск в ширину.
В общем это небезопасно, хотя для некоторых коллекций может быть. Очевидная альтернатива - использовать какой-то цикл for. Но вы не сказали, какую коллекцию используете, так что это может быть, а может и нет.
ИМХО, более безопасным способом было бы создать новую коллекцию, перебирать данную коллекцию, добавляя каждый элемент в новую коллекцию, а также добавляя дополнительные элементы в новую коллекцию, наконец возвращение новой коллекции.
Я предпочитаю обрабатывать коллекции функционально, а не изменять их на месте. Это позволяет полностью избежать подобных проблем, а также проблем с псевдонимом и других сложных источников ошибок.
Я бы реализовал это так:
List<Thing> expand(List<Thing> inputs) {
List<Thing> expanded = new ArrayList<Thing>();
for (Thing thing : inputs) {
expanded.add(thing);
if (needsSomeMoreThings(thing)) {
addMoreThingsTo(expanded);
}
}
return expanded;
}
Использование итераторов ... нет, я так не думаю. Вам нужно будет собрать что-то вроде этого:
Collection< String > collection = new ArrayList< String >( Arrays.asList( "foo", "bar", "baz" ) );
int i = 0;
while ( i < collection.size() ) {
String curItem = collection.toArray( new String[ collection.size() ] )[ i ];
if ( curItem.equals( "foo" ) ) {
collection.add( "added-item-1" );
}
if ( curItem.equals( "added-item-1" ) ) {
collection.add( "added-item-2" );
}
i++;
}
System.out.println( collection );
Какие результаты:
[foo, bar, baz, добавлен-элемент-1, добавлен-элемент-2]
Вы также можете посмотреть некоторые из более специализированных типов, например ListIterator , NavigableSet и (если вас интересуют карты) NavigableMap .
Здесь есть две проблемы:
Первая проблема заключается в добавлении в коллекцию
после возврата Iterator
. Как уже упоминалось, не существует определенного поведения при изменении базовой Collection
, как указано в документации для Iterator.remove
:
... Поведение итератора является не указано, если основной коллекция изменяется, пока итерация выполняется любым способом кроме вызова этого метода.
Вторая проблема заключается в том, что даже если можно было бы получить Iterator
и затем вернуться к тому же элементу, в котором находился Iterator
, нет гарантия порядка итераций, как указано в документации метода Collection.iterator
:
... Нет никаких гарантий относительно порядок, в котором элементы возвращается (если эта коллекция не является экземпляр некоторого класса, который предоставляет гарантия).
Например, у нас есть список [1, 2, 3, 4]
.
Допустим, 5
был добавлен, когда Итератор
находился на 3
, и каким-то образом мы получаем Итератор
, который может возобновить итерацию с 4
. Однако нет гарантии, что 5
появится после 4
. Порядок итераций может быть [5, 1, 2, 3, 4]
- тогда итератор все равно пропустит элемент 5
.
Поскольку нет гарантии для поведение, нельзя предполагать, что что-то произойдет определенным образом.
Одной из альтернатив может быть отдельная Коллекция
, к которой могут быть добавлены вновь созданные элементы, а затем итерация по этим элементам:
Collection<String> list = Arrays.asList(new String[]{"Hello", "World!"});
Collection<String> additionalList = new ArrayList<String>();
for (String s : list) {
// Found a need to add a new element to iterate over,
// so add it to another list that will be iterated later:
additionalList.add(s);
}
for (String s : additionalList) {
// Iterate over the elements that needs to be iterated over:
System.out.println(s);
}
Править
Доработав ответ Avi , можно поставить в очередь элементы, которые мы хотим перебрать, и удалить элементы, пока в очереди есть элементы. Это позволит «перебирать» новые элементы в дополнение к исходным.
Давайте посмотрим, как это будет работать.
Концептуально, если у нас есть следующие элементы в очереди:
[1, 2, 3, 4]
И, когда мы удаляем 1
, мы решаем добавить 42
, очередь будет следующей :
[2, 3, 4, 42]
Поскольку очередь представляет собой структуру данных FIFO (первый пришел - первый вышел), такой порядок является типичным. (Как отмечено в документации к интерфейсу Queue
, это не является необходимостью в Queue
. Возьмем случай PriorityQueue
, который упорядочивает элементы по их естественному упорядочение, так что это не FIFO.)
Ниже приведен пример использования LinkedList
(который представляет собой Queue
), чтобы пройти все элементы вместе с добавленными дополнительными элементами во время удаления. Как и в примере выше, элемент 42
добавляется, когда элемент 2
удаляется:
Queue<Integer> queue = new LinkedList<Integer>();
queue.add(1);
queue.add(2);
queue.add(3);
queue.add(4);
while (!queue.isEmpty()) {
Integer i = queue.remove();
if (i == 2)
queue.add(42);
System.out.println(i);
}
Результат следующий:
1
2
3
4
42
Как и предполагалось, элемент 42
, который был добавлен, когда мы нажали . 2
появилось.
Помимо решения с использованием дополнительного списка и вызова addAll для вставки новых элементов после итерации (например, решение пользователя Nat), вы также можете использовать параллельные коллекции, такие как CopyOnWriteArrayList .
Метод итератора в стиле «моментальный снимок» использует ссылку на состояние массива в момент создания итератора. Этот массив никогда не изменяется в течение времени жизни итератора, поэтому вмешательство невозможно, и итератор гарантированно не вызовет ConcurrentModificationException.
С помощью этой специальной коллекции (обычно используемой для одновременного доступа) можно манипулировать базовым списком во время итерации по Это. Однако итератор не отразит изменения.
Это лучше, чем другое решение? Наверное, нет, я не
На самом деле это довольно легко. Просто подумайте оптимально. Я верю оптимальному способу:
for (int i=0; i<list.size(); i++) {
Level obj = list.get(i);
//Here execute yr code that may add / or may not add new element(s)
//...
i=list.indexOf(obj);
}
В следующем примере отлично работает в наиболее логическом случае - когда вам не нужно повторять добавленные новые элементы перед элементом итерации. Об добавленных элементах после элемента итерации - там вы можете не повторить их. В этом случае вы должны просто добавить / или расширить объект YR с флагом, который помечет их не имитировать их.