कौन सा अधिक कुशल है, प्रत्येक के लिए लूप, या एक इटरेटर?


206

किसी संग्रह को पार करने का सबसे कुशल तरीका कौन सा है?

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

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

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


मुझे लगता है कि इसके जावा और टेम्प्लेटिंग तंत्र में सिंटैक्टिक शुगर की तुलना में थोड़ा अधिक अंतर नहीं है
हसन सैयद

संभावित डुप्लिकेट: stackoverflow.com/questions/89891/…
OMG Ponies

2
@OMG पॉनीज़: मुझे विश्वास नहीं है कि यह एक डुप्लिकेट है, क्योंकि यह लूप की तुलना इटरेटर से नहीं करता है, बल्कि यह पूछता है कि कलेक्शन पुनरावृत्तियों को क्यों लौटाते हैं, बजाय सीधे वर्ग पर पुनरावृत्त होने के।
पॉल वैगलैंड

जवाबों:


264

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

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

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

फिर लूप, या पुनरावृत्ति के लिए नया, अंतर्निहित डेटा संरचना के आधार पर बहुत अधिक कुशल हो सकता है। इसका कारण यह है कि कुछ डेटा संरचनाओं के लिए, get(i)एक O (n) ऑपरेशन है, जो लूप को O (n 2 ) ऑपरेशन बनाता है । एक पारंपरिक लिंक्ड सूची ऐसी डेटा संरचना का एक उदाहरण है। सभी पुनरावृत्तियों की एक मूलभूत आवश्यकता है कि 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

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


4
मेरा मानना ​​है कि वह इसके विपरीत कह रहा था, कि foo.get (i) बहुत कम कुशल हो सकता है। लिंक्डलिस्ट के बारे में सोचो। यदि आप एक LinkedList के बीच में एक foo.get (i) करते हैं, तो मुझे प्राप्त करने के लिए सभी पिछले नोड्स को पीछे करना होगा। दूसरी ओर, एक इटरेटर, अंतर्निहित डेटा संरचना का एक हैंडल रखेगा और आपको एक बार में नोड्स पर चलने की अनुमति देगा।
माइकल क्रुक्लिस

1
बड़ी बात नहीं है, लेकिन एक for(int i; i < list.size(); i++) {स्टाइल लूप को भी list.size()प्रत्येक पुनरावृत्ति के अंत में मूल्यांकन करना होगा , अगर इसका उपयोग किया जाता है तो यह कभी-कभी list.size()पहले परिणाम को कैश करने के लिए अधिक कुशल होता है ।
ब्रेट रायन

3
असल में, मूल कथन ArrayList और उन सभी अन्य लोगों के मामले के लिए भी सही है, जो रैंडम असफल इंटरफ़ेस को लागू करते हैं। "सी-स्टाइल" लूप, इटरेटर-आधारित एक से तेज है। docs.oracle.com/javase/7/docs/api/java/util/RandomAccess.html
andresp

4
इटरेटर दृष्टिकोण के बजाय पुराने सी-स्टाइल लूप का उपयोग करने का एक कारण, भले ही यह फ़ार्च या डेजारगाइड संस्करण हो, कचरा है। कई डेटा संरचनाएं .iterator () कहे जाने पर एक नए Iterator को तुरंत रोक देती हैं, हालांकि उन्हें C- स्टाइल लूप का उपयोग करके आवंटन-मुक्त तक पहुँचा जा सकता है। यह कुछ उच्च-प्रदर्शन वातावरणों में महत्वपूर्ण हो सकता है, जहां कोई आवंटनकर्ता या (बी) कचरा संग्रह से बचने की कोशिश कर रहा है।
दान

3
ArrayLists के लिए, एक अन्य टिप्पणी के रूप में, (int i = 0 ....) लूप लगभग 2 पुनरावृत्ति की तुलना में या it (:) दृष्टिकोण का उपयोग करने की तुलना में तेजी है, इसलिए यह वास्तव में अंतर्निहित संरचना पर निर्भर करता है। और एक साइड नोट के रूप में, हैशसेट को पुनरावृत्त करना भी बहुत महंगा है (एक ऐरे सूची से बहुत अधिक), इसलिए प्लेग जैसे (यदि आप कर सकते हैं) से बचें।
सिंह

106

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

जैसे

यह ठीक है:

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

12
यह बहुत सच है, भले ही यह सीधे तौर पर मेरे द्वारा दिए गए प्रश्न का उत्तर नहीं देता है, इसे जानकारीपूर्ण होने के लिए +1 दिया है, और तार्किक अनुवर्ती प्रश्न का उत्तर दिया है।
पॉल वैगलैंड

1
हां हम फॉर्च्यूनर लूप के साथ संग्रह तत्वों तक पहुंच सकते हैं, लेकिन हम उन्हें हटा नहीं सकते हैं, लेकिन हम Iterator वाले तत्वों को निकाल सकते हैं।
आकाश ५२ Ak Ak

22

पॉल के स्वयं के उत्तर पर विस्तार करने के लिए, उन्होंने प्रदर्शित किया है कि उस विशेष संकलक पर बाइटकोड समान है (संभवतः सूर्य के जेवैक?), लेकिन अलग-अलग संकलक एक ही बायोटेक उत्पन्न करने की गारंटी नहीं देते हैं , है ना? यह देखने के लिए कि दोनों के बीच वास्तविक अंतर क्या है, आइए सीधे स्रोत पर जाएं और जावा भाषा विशिष्टता की जांच करें, विशेष रूप से 14.14.2, "बयान के लिए बढ़ाया" :

बढ़ाया गया forविवरण forफॉर्म के मूल विवरण के बराबर है :

for (I #i = Expression.iterator(); #i.hasNext(); ) {
    VariableModifiers(opt) Type Identifier = #i.next();    
    Statement 
}

दूसरे शब्दों में, जेएलएस द्वारा यह आवश्यक है कि दोनों समान हों। सिद्धांत रूप में, जिसका अर्थ है कि बाइटकोड में सीमांत अंतर हो सकता है, लेकिन वास्तव में लूप के लिए बढ़ाया जाना आवश्यक है:

  • .iterator()विधि का आह्वान करें
  • उपयोग .hasNext()
  • के माध्यम से स्थानीय चर उपलब्ध कराएं .next()

तो, दूसरे शब्दों में, सभी व्यावहारिक उद्देश्यों के लिए बाइटकोड समान, या लगभग-समान होगा। किसी भी संकलक कार्यान्वयन की परिकल्पना करना कठिन है, जिसके परिणामस्वरूप दोनों के बीच कोई महत्वपूर्ण अंतर होगा।


वास्तव में, मैंने जो परीक्षण किया था वह एक्लिप्स कंपाइलर के साथ था, लेकिन आपका सामान्य बिंदु अभी भी खड़ा है। +1
पॉल वैगलैंड

3

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

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

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

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

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

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


1

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

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

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,
और main()उपयोग किए गए, के disassembled bytecode को पढ़ते हैं javap -c Whatever:

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.next()और लूप जारी रखता है

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

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

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

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

यहां छवि विवरण दर्ज करें


0

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

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

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

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

अब वापस पाश और पुनरावृत्ति के मामले के लिए।

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

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


1
अंतर के बारे में आपका कथन सत्य नहीं है, क्योंकि प्रत्येक लूप के लिए एक पुनरावृत्त पानी के नीचे का उपयोग किया जाता है, और ऐसा ही व्यवहार होता है।
पॉल वैगलैंड

@ वॉल्ट वैगलैंड, मैंने अपना उत्तर संशोधित किया है जो गलती को इंगित करने के लिए आपको धन्यवाद देता है
eccentricCoder

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

@Paul Wagland भले ही आप प्रत्येक लूप के लिए डिफ़ॉल्ट कार्यान्वयन का उपयोग करते हैं जो एक पुनरावृत्ति का उपयोग करता है, यह तब भी फेंक देगा और अपवाद करेगा यदि u समवर्ती संचालन के दौरान हटाने () विधि का उपयोग करने का प्रयास करता है। यहां
सनकी 20

1
प्रत्येक लूप के साथ, आपको पुनरावृत्त तक पहुंच प्राप्त नहीं होती है, इसलिए आप इसे हटा नहीं सकते। लेकिन वह बिंदु के बगल में है, आपके उत्तर में आप दावा करते हैं कि एक धागा सुरक्षित है, जबकि दूसरा नहीं है। भाषा विनिर्देश के अनुसार वे समान हैं, इसलिए वे दोनों केवल अंतर्निहित संग्रह के रूप में थ्रेड सुरक्षित हैं।
पॉल वैगलैंड

-8

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


कृपया अपने बयानों का समर्थन करने के लिए कुछ उदाहरण दें।
राजेश किटी

@ चंदन क्षमा करें लेकिन आपने जो लिखा है वह गलत है। उदाहरण के लिए: std :: वेक्टर भी एक संग्रह है लेकिन इसकी पहुंच O (1) है। तो एक वेक्टर पर पाश के लिए एक पारंपरिक सिर्फ O (n) है। मुझे लगता है कि आप कहना चाहते हैं, अगर अंडरलेइंग कंटेनर की पहुंच ओ (एन) की एक्सेस लागत है, तो यह एसटी :: सूची के लिए है, जहां ओ (एन ^ 2) की जटिलता है। उस स्थिति में पुनरावृत्तियों का उपयोग करने से ओ (एन) की लागत कम हो जाएगी, क्योंकि पुनरावृत्त तत्वों तक सीधे पहुंच की अनुमति देता है।
कैसर

यदि आप समय अंतर की गणना करते हैं, तो सुनिश्चित करें कि दोनों सेटों को क्रमबद्ध किया गया है (या काफी वितरित यादृच्छिक अनसोल्ड) और प्रत्येक सेट के लिए दो बार परीक्षण चलाएं और प्रत्येक के दूसरे रन की गणना करें। इसके साथ अपनी टाइमिंग फिर से जांचें (इसकी एक लंबी व्याख्या कि आपको दो बार टेस्ट चलाने की आवश्यकता क्यों है)। आपको यह प्रदर्शित करने की आवश्यकता है (शायद कोड के साथ) यह कैसे सच है। अन्यथा जहाँ तक मुझे ज्ञात है कि प्रदर्शन के मामले में दोनों समान हैं, लेकिन क्षमता नहीं।
ydobonebi
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.