java - जो अधिक कुशल है, प्रत्येक लूप के लिए, या एक पुनरावर्तक?




collections foreach (6)

अंतर प्रदर्शन में नहीं है, लेकिन क्षमता में है। संदर्भ का उपयोग करते समय सीधे आपके पास एक प्रकार के इटरेटर (जैसे List.iterator () बनाम List.listIterator () का उपयोग करके अधिक शक्ति होती है, हालांकि ज्यादातर मामलों में वे समान कार्यान्वयन वापस करते हैं)। आपके पास अपने लूप में इटरेटर का संदर्भ देने की क्षमता भी है। यह आपको ConcurrentModificationException प्राप्त किए बिना अपने संग्रह से आइटम निकालने जैसी चीजों को करने की अनुमति देता है।

जैसे

यह ठीक है:

Set<Object> set = new HashSet<Object>();
// add some items to the set

Iterator<Object> setIterator = set.iterator();
while(setIterator.hasNext()){
     Object o = setIterator.next();
     if(o meets some condition){
          setIterator.remove();
     }
}

ऐसा नहीं है, क्योंकि यह एक समवर्ती संशोधन अपवाद फेंक देगा:

Set<Object> set = new HashSet<Object>();
// add some items to the set

for(Object o : set){
     if(o meets some condition){
          set.remove(o);
     }
}

संग्रह को पार करने का सबसे प्रभावी तरीका कौन सा है?

List<Integer>  a = new ArrayList<Integer>();
for (Integer integer : a) {
  integer.toString();
}

या

List<Integer>  a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();) {
   Integer integer = (Integer) iterator.next();
   integer.toString();
}

कृपया ध्यान दें, कि यह इस का सही डुप्लिकेट नहीं है, this , this , या this , हालांकि अंतिम प्रश्न के उत्तर में से एक निकट आता है। इसका कारण यह है कि यह एक डुप्ली नहीं है, यह है कि इनमें से अधिकतर लूप की तुलना कर रहे हैं जहां आप इटरेटर का उपयोग करने के बजाय लूप के अंदर get(i) कॉल करते हैं।

जैसा कि Meta पर सुझाया गया है, मैं इस प्रश्न का उत्तर पोस्ट कर दूंगा।


इटरेटर जावा कलेक्शन फ्रेमवर्क में एक इंटरफेस है जो संग्रह पर ट्रैवर्स या फिर से चलाने के तरीकों को प्रदान करता है।

इटेटरेटर और लूप दोनों समान कार्य करते हैं जब आपका उद्देश्य केवल अपने तत्वों को पढ़ने के लिए संग्रह पर जाकर होता है।

for-each लिए संग्रह पर फिर से शुरू करने का एक ही तरीका है।

उदाहरण के लिए:

List<String> messages= new ArrayList<>();

//using for-each loop
for(String msg: messages){
    System.out.println(msg);
}

//using iterator 
Iterator<String> it = messages.iterator();
while(it.hasNext()){
    String msg = it.next();
    System.out.println(msg);
}

और प्रत्येक लूप के लिए केवल इटरेटर इंटरफ़ेस को लागू करने वाली वस्तुओं पर उपयोग किया जा सकता है।

अब लूप और इटरेटर के मामले में वापस।

अंतर तब आता है जब आप संग्रह को संशोधित करने का प्रयास करते हैं। इस मामले में, इटेटरेटर इसकी असफल संपत्ति के कारण अधिक कुशल है । अर्थात। यह अगले तत्व पर पुन: प्रयास करने से पहले अंतर्निहित संग्रह की संरचना में किसी भी संशोधन के लिए जांच करता है। यदि कोई संशोधन मिला है, तो यह ConcurrentModificationException फेंक देगा।

(नोट: इटेटरेटर की यह कार्यक्षमता केवल java.util पैकेज में संग्रह कक्षाओं के मामले में लागू होती है। यह समवर्ती संग्रह के लिए लागू नहीं है क्योंकि वे प्रकृति द्वारा असफल-सुरक्षित हैं)


फोरच अंडरहुड इटेटरेटर बना रहा iterator , जिसे नेक्स्ट () कहा जाता है और मूल्य प्राप्त करने के लिए अगला () को कॉल कर रहा है; प्रदर्शन के साथ समस्या केवल तभी आती है जब आप कुछ ऐसा उपयोग कर रहे हैं जो RandomomAccess लागू करता है।

for (Iterator<CustomObj> iter = customList.iterator(); iter.hasNext()){
   CustomObj custObj = iter.next();
   ....
}

इटरेटर-आधारित लूप के साथ प्रदर्शन समस्याएं इसलिए हैं क्योंकि यह है:

  1. सूची खाली होने पर भी ऑब्जेक्ट आवंटित करना ( Iterator<CustomObj> iter = customList.iterator(); );
  2. iter.hasNext() लूप के प्रत्येक पुनरावृत्ति के दौरान एक invokeInterface वर्चुअल कॉल है (सभी कक्षाओं के माध्यम से जाओ, फिर कूद से पहले विधि तालिका लुकअप करें)।
  3. hasNext() कॉल को hasNext() के लिए इटरेटर को कार्यान्वित करने के लिए कम से कम 2 फ़ील्ड लुकअप करना है: # 1 वर्तमान गिनती प्राप्त करें और # 2 कुल गिनती प्राप्त करें
  4. बॉडी लूप के अंदर, एक और invokeInterface वर्चुअल कॉल iter.next (इसलिए: सभी कक्षाओं के माध्यम से जाएं और कूदने से पहले विधि तालिका लुकअप करें) और साथ ही फील्ड लुकअप करना होगा: # 1 इंडेक्स प्राप्त करें और # 2 प्राप्त करें इसमें ऑफसेट करने के लिए सरणी के संदर्भ (प्रत्येक पुनरावृत्ति में)।

एक संभावित अनुकूलन कैश किए गए आकार लुकअप के साथ एक index iteration पर स्विच करना है :

for(int x = 0, size = customList.size(); x < size; x++){
  CustomObj custObj = customList.get(x);
  ...
}

हमारे पास है:

  1. एक आवेषण इंटरफ़ेस वर्चुअल विधि आकार प्राप्त करने के लिए लूप के प्रारंभिक निर्माण पर customList.size() को कॉल करें
  2. लूप के लिए शरीर के दौरान customList.get(x) प्राप्त करें विधि, जो सरणी के लिए फ़ील्ड लुकअप है और फिर सरणी में ऑफ़सेट कर सकती है

हमने विधि कॉल, फ़ील्ड लुकअप का एक टन घटा दिया। यह आप LinkedList या किसी ऐसे चीज़ के साथ नहीं करना चाहते हैं जो RandomAccess संग्रह obj नहीं है, अन्यथा customList.get(x) कुछ पुनरावृत्ति पर customList.get(x) को पार करने के लिए कुछ ऐसी चीज में बदल जाएगा।

यह सही है जब आप जानते हैं कि कोई RandomAccess आधारित सूची संग्रह है।


यदि आप सभी मूल्यों को पढ़ने के लिए संग्रह पर भटक रहे हैं, तो एक इटरेटर या लूप सिंटैक्स के लिए नया उपयोग करने के बीच कोई अंतर नहीं है, क्योंकि नया वाक्यविन्यास केवल पानी के नीचे इटरेटर का उपयोग करता है।

हालांकि, आपका मतलब है कि पुराने "सी-स्टाइल" लूप को लूप द्वारा:

for(int i=0; i<list.size(); i++) {
   Object o = list.get(i);
}

फिर अंतर्निहित डेटा संरचना के आधार पर लूप, या इटरेटर के लिए नया बहुत अधिक कुशल हो सकता है। इसका कारण यह है कि कुछ डेटा संरचनाओं के लिए, get(i) एक ओ (एन) ऑपरेशन है, जो लूप को ओ (एन 2 ) ऑपरेशन बनाता है। एक पारंपरिक लिंक्ड सूची ऐसी डेटा संरचना का एक उदाहरण है। सभी iterators एक मौलिक आवश्यकता के रूप में है कि next() एक ओ (1) ऑपरेशन होना चाहिए, लूप ओ (एन) बनाते हैं।

यह सत्यापित करने के लिए कि लेटर सिंटैक्स के लिए नए द्वारा इटरेटर का उपयोग पानी के नीचे किया जाता है, निम्नलिखित दो जावा स्निपेट से उत्पन्न बाइटकोड की तुलना करें। सबसे पहले लूप के लिए:

List<Integer>  a = new ArrayList<Integer>();
for (Integer integer : a)
{
  integer.toString();
}
// Byte code
 ALOAD 1
 INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator;
 ASTORE 3
 GOTO L2
L3
 ALOAD 3
 INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object;
 CHECKCAST java/lang/Integer
 ASTORE 2 
 ALOAD 2
 INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String;
 POP
L2
 ALOAD 3
 INVOKEINTERFACE java/util/Iterator.hasNext()Z
 IFNE L3

और दूसरा, इटेटरेटर:

List<Integer>  a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();)
{
  Integer integer = (Integer) iterator.next();
  integer.toString();
}
// Bytecode:
 ALOAD 1
 INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator;
 ASTORE 2
 GOTO L7
L8
 ALOAD 2
 INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object;
 CHECKCAST java/lang/Integer
 ASTORE 3
 ALOAD 3
 INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String;
 POP
L7
 ALOAD 2
 INVOKEINTERFACE java/util/Iterator.hasNext()Z
 IFNE L8

जैसा कि आप देख सकते हैं, जेनरेट बाइट कोड प्रभावी रूप से समान है, इसलिए किसी भी फॉर्म का उपयोग करने के लिए कोई प्रदर्शन दंड नहीं है। इसलिए, आपको लूप का रूप चुनना चाहिए जो आपके लिए सबसे सौंदर्यपूर्ण रूप से आकर्षक है, ज्यादातर लोगों के लिए जो प्रत्येक लूप के लिए होंगे, क्योंकि इसमें कम बॉयलरप्लेट कोड है।


संग्रह के साथ काम करते समय हमें लूप के लिए परंपरागत उपयोग से बचना चाहिए। सरल कारण जो मैं दूंगा वह यह है कि लूप की जटिलता ओ (वर्ग (एन)) और इटरेटर की जटिलता या यहां तक ​​कि लूप के लिए बढ़ाया गया है ओ (एन) है। तो यह एक प्रदर्शन अंतर देता है .. बस कुछ 1000 वस्तुओं की एक सूची लें और दोनों तरीकों से इसका प्रिंट करें। और निष्पादन के लिए समय अंतर भी मुद्रित करें। आप अंतर देख सकते हैं।


foreach वैसे भी हुड के तहत iterators का उपयोग करता है। यह वास्तव में सिर्फ वाक्य रचनात्मक चीनी है।

निम्नलिखित कार्यक्रम पर विचार करें:

import java.util.List;
import java.util.ArrayList;

public class Whatever {
    private final List<Integer> list = new ArrayList<>();
    public void main() {
        for(Integer i : list) {
        }
    }
}

चलो इसे javac Whatever.java साथ संकलित करते हैं,
और javap -c Whatever का उपयोग करके main() के अलग-अलग बाइटकोड को पढ़ें:

public void main();
  Code:
     0: aload_0
     1: getfield      #4                  // Field list:Ljava/util/List;
     4: invokeinterface #5,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
     9: astore_1
    10: aload_1
    11: invokeinterface #6,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
    16: ifeq          32
    19: aload_1
    20: invokeinterface #7,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
    25: checkcast     #8                  // class java/lang/Integer
    28: astore_2
    29: goto          10
    32: return

हम देख सकते हैं कि foreach एक प्रोग्राम के लिए संकलित करता है जो:

  • List.iterator() का उपयोग कर इटरेटर बनाता है
  • अगर Iterator.hasNext() : Iterator.hasNext() आमंत्रित करता है और लूप जारी रहता है

"इस बेकार लूप को संकलित कोड से ऑप्टिमाइज़ क्यों नहीं किया जाता है? हम देख सकते हैं कि यह सूची आइटम के साथ कुछ भी नहीं करता है": ठीक है, आपके लिए इस तरह के कोड को कोड करना संभव है .iterator() दुष्प्रभाव हैं, या ऐसा है कि .hasNext() दुष्प्रभाव या सार्थक परिणाम हैं।

आप आसानी से कल्पना कर सकते हैं कि किसी डेटाबेस से स्क्रॉल करने योग्य क्वेरी का प्रतिनिधित्व करने वाला एक .hasNext() कुछ .hasNext() कर सकता है। .hasNext() (डेटाबेस से संपर्क करने या कर्सर को बंद करने की तरह, क्योंकि आप परिणाम सेट के अंत तक पहुंच गए हैं)।

तो, भले ही हम साबित कर सकें कि लूप बॉडी में कुछ भी नहीं होता है ... यह साबित करने के लिए कि यह सार्थक / परिणामी होता है, यह कुछ महंगा (अव्यवस्थित?) होता है। कंपाइलर को इस खाली लूप बॉडी को प्रोग्राम में छोड़ना है।

सबसे अच्छा हम उम्मीद कर सकते हैं कि एक संकलक चेतावनी होगी । यह दिलचस्प है कि javac -Xlint:all Whatever.java हमें इस खाली पाश शरीर के बारे में चेतावनी नहीं देता है। IntelliJ IDEA हालांकि करता है। माना जाता है कि मैंने ग्रहण कंपाइलर का उपयोग करने के लिए इंटेलिजे को कॉन्फ़िगर किया है, लेकिन ऐसा कारण नहीं हो सकता है।







foreach