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)
ความหมาย indexes = [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
) นี่เป็นแบบไม่โอพีที่นี่ ฉันหายไปตรงไหนที่นี่?
System.arraycopy(es, end, es, w, size - end)
เป็นรายละเอียดการดำเนินงานพื้นฐานของremoveIf
? ฉันเกือบจะรู้สึกว่าฉันกำลังอ่านคำตอบของคำถามอื่นในระหว่างนั้น (อ่านความคิดเห็นด้านบน) ฉันรู้สึกว่ามันจบลงด้วยคำถามเล็ก ๆ น้อย ๆ ในที่สุด เป็นอย่างนั้นเหรอ?
System.arrayCopy
นั้น อย่างไรก็ตามมันเป็นการเดินทางที่สนุกผ่านรายละเอียด (ชุดบิตภายในนั้นกลับกลายเป็นความคิดเดียวกันjava.util.BitSet
)
range
... ) และฉันจะยอมรับมัน
java.util.BitSet
ได้ สำหรับฉันแล้วการนำการดำเนินการกลับมาใช้ใหม่BitSet
ไม่ได้ดูดีกว่าของเดิมอย่างชัดเจน พลาดโอกาสในการข้ามคำทั้งหมดไป