removeIf实现细节


9

我有一个小的实施细节问题,我在中无法理解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每次调用时removeArrayList都必须将基础层置于“良好”状态,这意味着内部数组实际上会发生变化。同样,每次调用时remove,都会在System::arrayCopy内部进行调用。

相比之下removeIf更聪明。由于它在内部进行迭代,因此可以使事情更优化。它这样做的方式很有趣。

它首先计算应该从中删除元素的索引。这是通过首先计算一个微小BitSetlong值数组来完成的,其中每个索引处都驻留一个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::testtrue)。

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。我在这里想念什么特例?


2
我只是每天浪费1/2来了解这些细节,这一点非常明显,这种方法也用于其他地方。我是白痴:|
尤金

老实说,你让我感到困惑。您是否想将System.arraycopy(es, end, es, w, size - end)用作的底层实现细节removeIf?我几乎觉得,我正在阅读介于两者之间的其他问题的答案。(阅读上面的评论)我觉得它最终变成了一个琐碎的问题。是这样吗?
纳曼

确切地说,@纳曼,就是那可怕的事System.arrayCopy。不过,这是一个有趣的细节之旅(内部位元组的想法与之相同java.util.BitSet
尤金

@Naman如果愿意,可以提供一个不是 NOOP 的答案(提示:range...),我会接受的。
尤金

1
Java 8中的@Eugene确实使用java.util.BitSet。对我来说,重新实现BitSet操作看起来并不比原始操作好得多。错过了跳过整个单词的机会。
Holger

Answers:


6

您正在寻找的列表,你可以调用特定的(普通)情况下removeIf的,是一样的ArrayList。仅在这种情况下,您可以假定end始终等于size

一个反例是:

ArrayList<Integer> l = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6, 7));
l.subList(2, 5).removeIf(i -> i%2 == 1);

同样,removeAllshiftTailOverGap使用与应用于时end可能不同的参数进行调用。sizesubList

致电时也会出现类似情况clear()。在这种情况下,在ArrayList本身上调用它时执行的实际操作非常琐碎,甚至不调用该shiftTailOverGap方法。仅当使用时,它才会l.subList(a, b).clear()removeRange(a, b)on 结束l,如您已经发现的那样,它将依次调用shiftTailOverGap(elementData, a, b)一个b小于的size

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.