У мене є невелике питання щодо детальної реалізації, яке я не розумію 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значень роблять це a 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операцій не виглядає суттєво краще, ніж оригінал. Можливість пропустити цілі слова пропущена.