loại bỏ nếu chi tiết thực hiện


9

Tôi có một câu hỏi chi tiết thực hiện nhỏ mà tôi không hiểu được ArrayList::removeIf. Tôi không nghĩ rằng tôi có thể đơn giản đặt nó theo cách mà không có một số điều kiện tiên quyết.

Như vậy: việc thực hiện về cơ bản là một số lượng lớn remove , không giống như ArrayList::remove. Một ví dụ sẽ làm cho mọi thứ dễ hiểu hơn nhiều. Hãy nói rằng tôi có danh sách này:

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

Và tôi muốn loại bỏ mọi yếu tố đồng đều. Tôi có thể làm:

Iterator<Integer> iter = list.iterator();
while (iter.hasNext()) {
    int elem = iter.next();
    if (elem % 2 == 0) {
         iter.remove();
    }
}

Hoặc :

list.removeIf(x -> x % 2 == 0);

Kết quả sẽ giống nhau, nhưng việc thực hiện rất khác nhau. Vì iteratorquan điểm của ArrayListmỗi lần tôi gọi remove, phần bên dưới ArrayListphải được đưa đến trạng thái "tốt", nghĩa là mảng bên trong sẽ thực sự thay đổi. Một lần nữa, trên mỗi cuộc gọi của remove, sẽ có các cuộc gọi System::arrayCopynội bộ.

Trên sự tương phản removeIflà thông minh hơn. Vì nó lặp đi lặp lại bên trong, nó có thể làm cho mọi thứ được tối ưu hóa hơn. Cách nó làm điều này là thú vị.

Đầu tiên, nó tính toán các chỉ mục nơi các yếu tố được cho là bị xóa khỏi. Điều này được thực hiện bằng cách tính toán đầu tiên BitSetmột mảng nhỏ các longgiá trị trong đó tại mỗi chỉ mục, nằm trong một 64 bitgiá trị (a long). Nhiều 64 bitgiá trị làm cho điều này a BitSet. Để đặt một giá trị ở một mức bù cụ thể, trước tiên bạn cần tìm ra chỉ mục trong mảng và sau đó đặt bit tương ứng. Điều này không phức tạp lắm. Giả sử bạn muốn đặt bit 65 và 3. Trước tiên, chúng tôi cần một long [] l = new long[2](vì chúng tôi đã vượt quá 64 bit, nhưng không quá 128):

|0...(60 more bits here)...000|0...(60 more bits here)...000|

Trước tiên, bạn tìm thấy chỉ mục: 65 / 64(họ thực sự làm 65 >> 6) và sau đó trong chỉ mục đó ( 1) đặt bit cần thiết:

1L << 65 // this will "jump" the first 64 bits, so this will actually become 00000...10. 

Điều tương tự cho 3. Như vậy, mảng dài sẽ trở thành:

|0...(60 more bits here)...010|0...(60 more bits here)...1000|

Trong mã nguồn, họ gọi đây là BitSet - deathRow(tên hay!).


Hãy lấy evenví dụ đó ở đây, nơilist = 2, 4, 6, 5, 5

  • họ lặp mảng và tính này deathRow(nơi Predicate::testtrue).

cái chếtRow = 7 (000 ... 111)

nghĩa là các chỉ mục = [0, 1, 2] sẽ bị xóa

  • bây giờ họ thay thế các phần tử trong mảng cơ bản dựa trên deathRow đó (không đi sâu vào chi tiết cách thực hiện)

mảng bên trong trở thành: [5, 5, 6, 5, 5]. Về cơ bản, họ di chuyển các phần tử được cho là vẫn ở phía trước mảng.


Cuối cùng tôi có thể mang lại câu hỏi.

Tại thời điểm này, họ biết:

 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

Đối với tôi, có một bước duy nhất để làm ở đây:

void getRidOfElementsFromWToEnd() {
    for(int i=w; i<end; ++i){
       es[i] = null;
    }
    size = w;
}

Thay vào đó, điều này xảy ra:

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

Tôi đã đổi tên các mục đích ở đây.

Điểm gọi là gì:

 System.arraycopy(es, end, es, w, size - end);

Đặc biệt size - end, vì end size tất cả thời gian - nó không bao giờ thay đổi (vì vậy điều này luôn luôn zero). Đây cơ bản là một NO-OP ở đây. Trường hợp góc nào tôi bị thiếu ở đây?


2
Tôi chỉ lãng phí 1/2 mỗi ngày để hiểu những chi tiết này, và điều này quá rõ ràng, phương pháp này cũng được sử dụng ở những nơi khác . Tôi là một thằng ngốc: |
Eugene

Thành thật mà nói, bạn để lại cho tôi bối rối. Là câu hỏi của bạn xung quanh việc sử dụng System.arraycopy(es, end, es, w, size - end)như là chi tiết thực hiện cơ bản của removeIf? Tôi gần như cảm thấy, tôi đang đọc một câu trả lời cho một số câu hỏi khác ở giữa. (Đọc bình luận ở trên) Tôi cảm thấy cuối cùng trong một câu hỏi tầm thường. Là vậy sao?
Naman

@ Naman chính xác, đó là về điều đó đáng sợ System.arrayCopy. Tuy nhiên, đó là một hành trình thú vị thông qua các chi tiết (bộ bit bên trong đó hóa ra có cùng ý tưởng với java.util.BitSet)
Eugene

@Naman nếu bạn muốn bạn có thể cung cấp câu trả lời trong đó không phải là NOOP (gợi ý: range...) và tôi sẽ chấp nhận nó.
Eugene

1
@Eugene trong Java 8, nó có sử dụng java.util.BitSet. Đối với tôi, việc thực hiện lại các BitSethoạt động không có vẻ tốt hơn đáng kể so với ban đầu. Cơ hội để bỏ qua toàn bộ từ đã bị bỏ lỡ.
Holger

Câu trả lời:


6

Bạn đang xem trường hợp cụ thể (phổ biến) mà danh sách, bạn gọi removeIf, giống như ArrayList. Chỉ trong trường hợp này, bạn có thể cho rằng endluôn luôn bằng size.

Một ví dụ ngược lại sẽ là:

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

Tương tự như vậy, removeAllsẽ gọi shiftTailOverGapvới một endđối số có thể khác với sizekhi được áp dụng cho a subList.

Một tình huống tương tự phát sinh khi bạn gọi clear(). Trong trường hợp đó, hoạt động thực tế, được thực hiện khi tự gọi nó ArrayList, rất tầm thường đến nỗi nó thậm chí không gọi shiftTailOverGapphương thức. Chỉ khi sử dụng một cái gì đó giống như l.subList(a, b).clear(), nó sẽ kết thúc ở removeRange(a, b)trên l, mà sẽ đến lượt nó, như bạn đã phát hiện ra chính mình, invoke shiftTailOverGap(elementData, a, b)với một bmà có thể nhỏ hơn size.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.