У меня есть небольшой вопрос о реализации, который я не могу понять в ArrayList::removeIf
. Я не думаю, что я могу просто сказать так, как есть, без каких-либо предварительных условий.
Как таковой: реализация в основном большая remove
, в отличие от ArrayList::remove
. Пример должен сделать вещи намного проще для понимания. Допустим, у меня есть этот список:
List<Integer> list = new ArrayList<>(); // 2, 4, 6, 5, 5
list.add(2);
list.add(4);
list.add(6);
list.add(5);
list.add(5);
И я хотел бы удалить каждый элемент, который является четным. Я мог бы сделать:
Iterator<Integer> iter = list.iterator();
while (iter.hasNext()) {
int elem = iter.next();
if (elem % 2 == 0) {
iter.remove();
}
}
Или :
list.removeIf(x -> x % 2 == 0);
Результат будет таким же, но реализация будет отличаться. Поскольку iterator
представление является видом ArrayList
, каждый раз, когда я вызываю remove
, базовый уровень ArrayList
должен быть приведен в «хорошее» состояние, означающее, что внутренний массив действительно изменится. Опять же, при каждом вызове remove
, будут звонкиSystem::arrayCopy
внутренние .
На контрасте removeIf
умнее. Поскольку он выполняет итерацию внутри, он может сделать вещи более оптимизированными. То, как это происходит, интересно.
Сначала он вычисляет индексы, из которых элементы должны быть удалены. Это делается сначала путем вычисления крошечного BitSet
массива long
значений, где в каждом индексе находится 64 bit
значение (a long
). Множественные 64 bit
значения делают это BitSet
. Чтобы установить значение с определенным смещением, сначала нужно найти индекс в массиве, а затем установить соответствующий бит. Это не очень сложно. Допустим, вы хотите установить биты 65 и 3. Сначала нам нужно long [] l = new long[2]
(потому что мы вышли за пределы 64 бит, но не более 128):
|0...(60 more bits here)...000|0...(60 more bits here)...000|
Сначала вы находите индекс: 65 / 64
(они на самом деле делают 65 >> 6
), а затем в этом индексе ( 1
) помещаете нужный бит:
1L << 65 // this will "jump" the first 64 bits, so this will actually become 00000...10.
То же самое для 3
. Таким образом, длинный массив станет:
|0...(60 more bits here)...010|0...(60 more bits here)...1000|
В исходном коде они называют этот BitSet - deathRow
(красивое имя!).
Давайте возьмем это even
пример здесь, гдеlist = 2, 4, 6, 5, 5
- они перебирают массив и вычисляют это
deathRow
(гдеPredicate::test
естьtrue
).
deathRow = 7 (000 ... 111)
означающие индексы = [0, 1, 2] должны быть удалены
- теперь они заменяют элементы в базовом массиве на основе этого deathRow (не вдаваясь в детали, как это делается)
Внутренний массив становится: [5, 5, 6, 5, 5]. В основном они перемещают элементы, которые должны оставаться перед массивом.
Я наконец могу внести в вопрос.
На данный момент они знают:
w -> number of elements that have to remain in the list (2)
es -> the array itself ([5, 5, 6, 5, 5])
end -> equal to size, never changed
Для меня здесь есть один шаг:
void getRidOfElementsFromWToEnd() {
for(int i=w; i<end; ++i){
es[i] = null;
}
size = w;
}
Вместо этого это происходит:
private void shiftTailOverGap(Object[] es, int w, int end) {
System.arraycopy(es, end, es, w, size - end);
for (int to = size, i = (size -= end - w); i < to; i++)
es[i] = null;
}
Я специально переименовал переменные здесь.
Какой смысл звонить:
System.arraycopy(es, end, es, w, size - end);
Тем более size - end
, что end
это size
все время - оно никогда не меняется (так всегда zero
). Это в основном NO-OP здесь. В каком угловом случае я здесь скучаю?
System.arraycopy(es, end, es, w, size - end)
качестве основных деталей реализацииremoveIf
? Я почти чувствовал, что я читал ответ на какой-то другой вопрос между ними. (Читая комментарий выше) Я чувствую, что в конечном итоге он оказался в тривиальном вопросе. Это так?System.arrayCopy
. Тем не менее, это было забавное путешествие через детали (этот внутренний набор битов имеет ту же идею, что иjava.util.BitSet
)range
...), и я приму его.java.util.BitSet
. Для меня повторная реализацияBitSet
операций не выглядит значительно лучше, чем оригинал. Возможность пропустить целые слова была упущена.Ответы:
Вы смотрите на конкретный (общий) случай, когда список, к которому вы обращаетесь
removeIf
, совпадает сArrayList
. Только в этом случае можно предположить, чтоend
всегда равноsize
.Контрпримером будет:
Аналогично,
removeAll
вызоветshiftTailOverGap
сend
аргументом, который может отличаться отsize
того, который применяется кsubList
.Похожая ситуация возникает при звонке
clear()
. В этом случае фактическая операция, выполняемая при вызове ееArrayList
самой, настолько тривиальна, что она даже не вызываетshiftTailOverGap
метод. Только при использовании что - то подобноеl.subList(a, b).clear()
, это будет в конечном итоге наremoveRange(a, b)
наl
, что, в свою очередь, как вы уже выяснили сами, вызывайтеshiftTailOverGap(elementData, a, b)
сb
которой может быть меньшеsize
.источник