निष्कासन कार्यान्वयन विवरण


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, हर बार जब मैं कॉल remove, अंतर्निहित ArrayListजिसका अर्थ है कि भीतरी सरणी वास्तव में बदल जाएगा एक "अच्छा" राज्य के लिए लाया जा रहा है। फिर, हर एक कॉल पर remove, System::arrayCopyआंतरिक रूप से कॉल होगी ।

इसके विपरीत अधिक removeIfचालाक है। चूंकि यह आंतरिक रूप से पुनरावृत्ति करता है, इसलिए यह चीजों को अधिक अनुकूलित बना सकता है। जिस तरह से यह करता है वह दिलचस्प है।

यह पहले उन अनुक्रमितों की गणना करता है जहां तत्वों को हटा दिया जाना चाहिए। यह पहले एक छोटे से गणना करके किया जाता है BitSet, longमूल्यों का एक सरणी जहां प्रत्येक सूचकांक पर, एक 64 bitमान (ए 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|

स्रोत कोड में वे इस बिटसेट को कहते हैं - deathRow(अच्छा नाम!)।


आइए उस evenउदाहरण को यहां लें, जहांlist = 2, 4, 6, 5, 5

  • वे सरणी को पुन: व्यवस्थित करते हैं और यह गणना करते हैं deathRow(जहां Predicate::testहै true)।

मौत = 7 (000 ... 111)

अर्थ अनुक्रमित = [0, 1, 2] हटाए जाने हैं

  • वे अब उस मृत्यु के आधार पर अंतर्निहित सरणी में तत्वों को प्रतिस्थापित करते हैं (विवरण में नहीं जा रहा है कि यह कैसे किया जाता है)

आंतरिक सरणी बन जाती है: [५, ५, ६, ५, ५]। मूल रूप से वे उन तत्वों को स्थानांतरित करते हैं जिन्हें सरणी के सामने रहना चाहिए।


मैं अंत में प्रश्न में ला सकता हूं।

इस समय, वे जानते हैं:

 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)
यूजीन

@ नमन यदि आप चाहें तो एक उत्तर दे सकते हैं, जहां वह नप नहीं है (संकेत: range...) और मैं इसे स्वीकार करूंगा।
यूजीन

1
जावा 8 में @ यूजीन, यह उपयोग करता है java.util.BitSet। मेरे लिए, BitSetऑपरेशन्स को फिर से लागू करना मूल से बेहतर नहीं लगता है। संपूर्ण शब्दों को छोड़ने का अवसर चूक गया है।
होल्गर

जवाबों:


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)एक साथ bजो की तुलना में छोटे हो सकता है size

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.