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, 매번 I 호 remove, 하부는 ArrayList내부 배열 실제로 변경할 것을 의미하는 "좋은"상태로하게되어야한다. ,의 모든 단일 호출마다 내부적 remove으로 호출이 있습니다 System::arrayCopy.

대조적 removeIf으로 더 똑똑합니다. 내부적으로 반복을 수행하므로 작업을보다 최적화 할 수 있습니다. 이것이하는 방식은 흥미 롭습니다.

먼저 요소를 제거 해야하는 인덱스를 계산합니다. 이것은 먼저 각 인덱스에서 값 (a )가 있는 작은 값 BitSet의 배열을 계산하여 수행됩니다 . 여러 값이 있으면이를 a로 만듭니다. 특정 오프셋에서 값을 설정하려면 먼저 배열에서 인덱스를 찾은 다음 해당 비트를 설정해야합니다. 이것은 매우 복잡하지 않습니다. 비트 65와 3을 설정한다고 가정 해 봅시다. 먼저 64 비트를 넘었지만 128을 넘지 않기 때문에 a가 필요합니다 .long64 bitlong64 bitBitSetlong [] l = new long[2]

|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|

소스 코드에서이 비트 세트 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입니다. 여기서 어떤 코너 사건이 빠져 있습니까?


2
나는이 세부 사항을 이해하는 데 하루에 1/2을 낭비했으며,이 방법은 다른 곳에서도 사용됩니다 . 나는 바보입니다 : |
유진

솔직히, 당신은 나를 혼란스럽게했다. System.arraycopy(es, end, es, w, size - end)의 기본 구현 세부 정보로 사용에 대한 질문 이 removeIf있습니까? 나는 거의 같은 느낌이 들었다. 나는 그 사이에 다른 질문에 대한 답을 읽고 있었다. (위의 의견을 읽음) 나는 그것이 사소한 질문으로 마침내 끝났다고 생각합니다. 그렇습니까?
Naman

@Naman 정확하게, 그것은 그 무서운 것에 관한 것이었다 System.arrayCopy. 그럼에도 불구하고 그것은 세부 사항을 통한 재미있는 여행이었습니다 (내부 비트 세트와 같은 아이디어가있는 것으로 나타났습니다 java.util.BitSet)
Eugene

@ Naman 원하는 경우 NOOP (힌트 : ...) 가 아닌 곳에서 답변을 제공 할 수 있으며 range받아 들일 것입니다.
유진

1
Java 8의 @Eugene에서는을 사용 java.util.BitSet합니다. 나에게, 작업의 재 구현 BitSet이 원래보다 훨씬 나아 보이지는 않습니다. 전체 단어를 건너 뛸 수있는 기회가 빠졌습니다.
Holger

답변:


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);

마찬가지로, 에 적용될 때 와 다를 수 있는 인수로 removeAll호출 합니다 .shiftTailOverGapendsizesubList

전화를 걸 때 비슷한 상황이 발생합니다 clear(). 이 경우, ArrayList그 자체로 호출 할 때 수행되는 실제 조작 은 너무 사소하여 shiftTailOverGap메소드를 호출하지도 않습니다 . 같은 것을 사용하는 경우 만 l.subList(a, b).clear(), 그것으로 끝날 것 removeRange(a, b)l호출 차례로 것입니다 당신은 이미 자신에서 발견되는 shiftTailOverGap(elementData, a, b)A를 b보다 작을 수있다 size.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.