एक प्रारंभिक क्षमता के साथ एक ArrayList क्यों शुरू करें?


149

का सामान्य निर्माता ArrayListहै:

ArrayList<?> list = new ArrayList<>();

लेकिन इसकी प्रारंभिक क्षमता के लिए एक पैरामीटर के साथ एक अतिभारित निर्माता भी है:

ArrayList<?> list = new ArrayList<>(20);

ArrayListप्रारंभिक क्षमता के साथ बनाने के लिए यह उपयोगी क्यों है जब हम कृपया इसे जोड़ सकते हैं?


17
क्या आपने ArrayList स्रोत कोड देखने की कोशिश की है?
अमितजी

@ जोकिम सॉयर: कभी-कभार हमें तब संज्ञान मिलता है जब हम स्रोत को ध्यान से पढ़ते हैं। मैं एक कोशिश दे रहा था अगर उसने स्रोत पढ़ा है। मैं आपका पहलू समझ गया। धन्यवाद।
अमितजी

ArrayList खराब प्रदर्शन की अवधि है, आप ऐसी संरचना का उपयोग क्यों करना चाहते हैं
पॉजिटिव

जवाबों:


196

यदि आप पहले से जानते हैं कि आकार क्या ArrayListहोने जा रहा है, तो प्रारंभिक क्षमता को निर्दिष्ट करना अधिक कुशल है। यदि आप ऐसा नहीं करते हैं, तो सूची बढ़ने के साथ आंतरिक सारणी को बार-बार पुन: प्राप्त करना होगा।

अंतिम सूची जितनी बड़ी होगी, उतने अधिक समय के लिए वास्तविक धनराशि से बचना होगा।

कहा कि, पूर्व-आवंटन के बिना भी, ए nके पीछे तत्वों को सम्मिलित करना ArrayListकुल O(n)समय लेने की गारंटी है । दूसरे शब्दों में, एक तत्व को जोड़ना एक परिशोधित स्थिर-समय ऑपरेशन है। यह आम तौर पर एक कारक के द्वारा सरणी के आकार में तेजी से वृद्धि करके प्राप्त किया जाता है 1.5। इस दृष्टिकोण के साथ, संचालन की कुल संख्या को दिखाया जा सकता हैO(n)


5
जबकि पूर्व के आवंटन में जाना जाता है आकार एक अच्छा विचार है, नहीं कर रही यह आम तौर पर भयानक नहीं है: आप के बारे में की आवश्यकता होगी लॉग (एन) के अंतिम आकार के साथ एक सूची के लिए फिर से आवंटन n है, जो एक बहुत नहीं है।
जोकिम सॉर

2
@PeterOlson काम के समय O(n log n)कर रही होगी । यह एक सकल overestimate है (हालांकि तकनीकी रूप से बड़े ओ के साथ सही होने के कारण यह एक ऊपरी बाध्य है)। यह कुल में s + s * 1.5 + s * 1.5 ^ 2 + ... + s * 1.5 ^ m (जैसे कि s * 1.5 ^ m <n * 1.5 ^ (m + 1)) को कॉपी करता है। मैं रकम पर अच्छा नहीं हूँ, इसलिए मैं आपको मेरे सिर के ऊपर से सटीक गणित नहीं दे सकता (कारक 2 के आकार बदलने के लिए, यह 2n है, इसलिए यह 1.5n दे सकता है या एक छोटा निरंतर ले सकता है), लेकिन यह नहीं है ' t यह देखने के लिए कि यह योग n की तुलना में सबसे अधिक स्थिर कारक पर है, बहुत अधिक स्क्वीटिंग करें। तो यह O (k * n) प्रतियां लेता है, जो निश्चित रूप से O (n) है। log nn

1
@delnan: उस के साथ बहस नहीं कर सकते! ;) बीटीडब्लू, मुझे आपका स्क्विटिंग तर्क बहुत पसंद आया; यह मेरे चाल की सूची में जोड़ देगा।
NPE

6
दोहरीकरण के साथ तर्क करना आसान है। मान लीजिए कि जब आप पूर्ण होते हैं, तो एक तत्व से शुरू होता है। मान लीजिए आप 8 तत्वों को सम्मिलित करना चाहते हैं। एक डालें (लागत: 1)। दो डालें - डबल, एक तत्व की प्रतिलिपि बनाएँ और दो डालें (लागत: 2)। तीन - डबल डालें, दो तत्वों को कॉपी करें, तीन डालें (लागत: 3)। चार डालें (लागत: 1)। पाँच डालें - डबल, चार तत्वों की प्रतिलिपि बनाएँ, पाँच डालें (लागत: 5)। छह, सात और आठ डालें (लागत: 3)। कुल लागत: 1 + 2 + 3 + 1 + 5 + 3 = 16, जो सम्मिलित तत्वों की संख्या से दोगुना है। इस स्केच से आप यह साबित कर सकते हैं कि औसत लागत सामान्य रूप से दो प्रति सम्मिलित है।
एरिक लिपर्ट

9
यही समय में लागत है । आप यह भी देख सकते हैं कि समय के साथ व्यर्थ अंतरिक्ष की मात्रा बदल गई, कुछ समय में ०% और कुछ समय में १००% के करीब। कारक को 2 से 1.5 या 4 या 100 में बदलना या जो भी व्यर्थ जगह की औसत मात्रा और प्रतिलिपि बनाने में बिताए समय की औसत मात्रा को बदलता है, लेकिन समय की जटिलता औसत पर रैखिक नहीं रहती है चाहे कारक क्या हो।
एरिक लिपिपर्ट

41

क्योंकि ArrayListएक गतिशील रूप से आकार देने वाली सरणी डेटा संरचना है, जिसका अर्थ है कि यह एक प्रारंभिक (डिफ़ॉल्ट) निश्चित आकार के साथ एक सरणी के रूप में कार्यान्वित किया जाता है। जब यह भर जाता है, तो सरणी को एक डबल आकार में विस्तारित किया जाएगा। यह ऑपरेशन महंगा है, इसलिए आप जितना संभव हो उतना कम चाहते हैं।

तो, यदि आप जानते हैं कि आपकी ऊपरी सीमा 20 आइटम है, तो 20 की प्रारंभिक लंबाई के साथ सरणी बनाना, डिफ़ॉल्ट के उपयोग की तुलना में बेहतर है, कहते हैं, 15 और फिर इसे आकार दें 15*2 = 30और विस्तार के लिए चक्र बर्बाद करते हुए केवल 20 का उपयोग करें।

PS - जैसा कि अमित जी कहते हैं, विस्तार कारक कार्यान्वयन विशिष्ट है (इस मामले में (oldCapacity * 3)/2 + 1)


9
यह वास्तव में हैint newCapacity = (oldCapacity * 3)/2 + 1;
अमित

25

Arraylist का डिफ़ॉल्ट आकार 10 है

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
    this(10);
    } 

इसलिए यदि आप 100 या अधिक रिकॉर्ड जोड़ने जा रहे हैं, तो आप स्मृति प्राप्ति के ओवरहेड को देख सकते हैं।

ArrayList<?> list = new ArrayList<>();    
// same as  new ArrayList<>(10);      

इसलिए यदि आपके पास Arraylist में संग्रहीत किए जाने वाले तत्वों की संख्या के बारे में कोई भी विचार है, तो Arraylist को 10 के साथ शुरू करने के बजाय उस आकार के साथ बनाना बेहतर होगा और फिर इसे बढ़ाना होगा।


इस बात की कोई गारंटी नहीं है कि भविष्य में JDK संस्करणों के लिए डिफ़ॉल्ट क्षमता हमेशा 10 ही होगी -private static final int DEFAULT_CAPACITY = 10
17:00 बजे vikingsteve करें

17

मैंने वास्तव में 2 महीने पहले विषय पर एक ब्लॉग पोस्ट लिखा था। लेख सी # के लिए है, List<T>लेकिन जावा का ArrayListएक समान कार्यान्वयन है। चूंकि ArrayListएक गतिशील सरणी का उपयोग करके कार्यान्वित किया जाता है, इसलिए यह मांग पर आकार में बढ़ जाता है। तो क्षमता निर्माण का कारण अनुकूलन उद्देश्यों के लिए है।

जब इन रेज़िस्टेंस ऑपरेशन में से एक होता है, तो ArrayList सरणी की सामग्री को नए सरणी में कॉपी करता है जो पुराने की क्षमता से दोगुना है। यह ऑपरेशन ओ (एन) समय में चलता है ।

उदाहरण

यहाँ एक उदाहरण दिया गया है कि ArrayListआकार में वृद्धि कैसे होगी:

10
16
25
38
58
... 17 resizes ...
198578
297868
446803
670205
1005308

इसलिए सूची की क्षमता के साथ शुरू होता है 10, जब 11 वीं वस्तु को जोड़ा जाता है, तो इसे बढ़ा दिया 50% + 1जाता है 16। 17 वें आइटम पर ArrayListफिर से 25और इसी तरह बढ़ा दिया जाता है । अब उस उदाहरण पर विचार करें जहाँ हम एक सूची बना रहे हैं जहाँ वांछित क्षमता पहले से ही ज्ञात है 1000000। बनाना ArrayListआकार निर्माता के बिना कॉल करेंगे ArrayList.add 1000000बार जो लेता हे (1) सामान्य रूप से या हे (एन) आकार बदलने पर।

1000000 + 16 + 25 + ... + 670205 + 1005308 = 4015851 संचालन

कंस्ट्रक्टर का उपयोग करके इसकी तुलना करें और फिर कॉलिंग करें ArrayList.addजो ओ (1) में चलने की गारंटी है ।

1000000 + 1000000 = 2000000 ऑपरेशन

जावा बनाम सी #

जावा ऊपर है, शुरू करने 10और प्रत्येक आकार बढ़ाने पर 50% + 1। C # पर शुरू होता है 4और प्रत्येक आवर्तन पर दोगुना अधिक आक्रामक रूप से बढ़ता है। 1000000सी # उपयोगों के लिए ऊपर से एक उदाहरण कहते हैं 3097084आपरेशनों।

संदर्भ


9

ArrayList के प्रारंभिक आकार को, उदाहरण के लिए ArrayList<>(100), आंतरिक मेमोरी के पुन: आबंटन की संख्या को घटाना होता है।

उदाहरण:

ArrayList example = new ArrayList<Integer>(3);
example.add(1); // size() == 1
example.add(2); // size() == 2, 
example.add(2); // size() == 3, example has been 'filled'
example.add(3); // size() == 4, example has been 'expanded' so that the fourth element can be added. 

जैसा कि आप ऊपर के उदाहरण में देखते हैं - ArrayListयदि आवश्यक हो तो विस्तार किया जा सकता है। यह आपको नहीं दिखाता है कि Arraylist का आकार आमतौर पर दोगुना हो जाता है (हालांकि ध्यान दें कि नया आकार आपके कार्यान्वयन पर निर्भर करता है)। निम्नलिखित ओरेकल से उद्धृत किया गया है :

"प्रत्येक ArrayList उदाहरण में एक क्षमता है। क्षमता सूची में तत्वों को संग्रहीत करने के लिए उपयोग किए जाने वाले सरणी का आकार है। यह हमेशा कम से कम सूची आकार जितना बड़ा होता है। तत्वों को एक ArrayList में जोड़ा जाता है, इसकी क्षमता स्वचालित रूप से बढ़ती है। विकास नीति का विवरण इस तथ्य से परे निर्दिष्ट नहीं किया जाता है कि किसी तत्व को जोड़ने पर निरंतर परिशोधित समय लागत होती है। "

जाहिर है, अगर आपके पास कोई विचार नहीं है कि आप किस प्रकार की सीमा धारण करेंगे, तो आकार सेट करना शायद एक अच्छा विचार नहीं होगा - हालांकि, यदि आपके पास एक विशिष्ट सीमा है, तो प्रारंभिक क्षमता निर्धारित करने से मेमोरी क्षमता बढ़ जाएगी ।


3

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


3

यह हर एक वस्तु के लिए वास्तविककरण के संभावित प्रयासों से बचने के लिए है।

int newCapacity = (oldCapacity * 3)/2 + 1;

आंतरिक रूप new Object[]से निर्मित है। जब आप सरणी सूची में तत्व जोड़ते
हैं new Object[]तो जेवीएम को बनाने के लिए प्रयास की आवश्यकता होती है । यदि आपके पास वास्तविककरण के लिए उपरोक्त कोड (कोई भी अहंकार जो आपको लगता है) नहीं है, तो हर बार जब आप आह्वान करते हैं arraylist.add()तो new Object[]बनाना पड़ता है जो कि व्यर्थ है और हम प्रत्येक और प्रत्येक ऑब्जेक्ट को जोड़ने के लिए 1 से आकार बढ़ाने के लिए समय खो रहे हैं। इसलिए Object[]निम्नलिखित सूत्र के साथ आकार में वृद्धि करना बेहतर है।
(जेएसएल ने हर बार 1 बढ़ने की बजाय गतिशील रूप से बढ़ने वाली सरणी सूची के लिए नीचे दिए गए forcasting सूत्र का उपयोग किया है। क्योंकि बढ़ने के लिए यह JVM द्वारा प्रयास लेता है)

int newCapacity = (oldCapacity * 3)/2 + 1;

ArrayList प्रत्येक के लिए reallocation प्रदर्शन नहीं करेगा add- यह पहले से ही आंतरिक रूप से कुछ विकास सूत्र का उपयोग करता है। इसलिए सवाल का जवाब नहीं है।
एह

@ मेरा उत्तर नकारात्मक परीक्षण के लिए है । कृपया लाइनों के बीच पढ़ें। मैंने कहा "यदि आपके पास वास्तविककरण के लिए उपरोक्त कोड (कोई भी अहंकार जो आपको लगता है) नहीं है तो हर बार जब आप arraylist.add () को आमंत्रित करते हैं तो नई वस्तु [] बनानी होगी जो व्यर्थ है और हम समय खो रहे हैं।" और कोड है int newCapacity = (oldCapacity * 3)/2 + 1;जो ArrayList वर्ग में मौजूद है। क्या आपको अब भी लगता है कि यह अनुत्तरित है?
अमितजी

1
मुझे अभी भी लगता है कि इसका उत्तर नहीं दिया गया है: प्रारंभिक क्षमता के लिए किसी भी मामले ArrayListमें परिशोधित रिएलोकेशन किसी भी मामले में होता है। और सवाल के बारे में है: प्रारंभिक क्षमता के लिए एक गैर-मानक मूल्य का उपयोग क्यों करें? इसके अलावा: "लाइनों के बीच पढ़ना" तकनीकी उत्तर में वांछित कुछ नहीं है। ;-)
एएच

@ मैं ऐसा जवाब दे रहा हूं, अगर हम एरियर लाईक में रिअलोकेशन प्रक्रिया नहीं करते तो क्या होता। तो इसका जवाब है। उत्तर की भावना को पढ़ने की कोशिश करें :-)। मुझे बेहतर पता है कि ArrayList में प्रारंभिक क्षमता के लिए किसी भी मूल्य के साथ किसी भी मामले में amortized reallocation होता है।
अमितजी

2

मुझे लगता है कि प्रत्येक ArrayList "10" की एक init क्षमता मूल्य के साथ बनाया गया है। तो वैसे भी, यदि आप एक ArrayList बनाते हैं तो बिना निर्माता की क्षमता के इसे डिफ़ॉल्ट मान के साथ बनाया जाएगा।


2

मैं इसका अनुकूलन कहूँगा। प्रारंभिक क्षमता के बिना ArrayList में ~ 10 खाली पंक्तियाँ होंगी और जब आप एक ऐड कर रहे हों तो इसका विस्तार होगा।

आपके द्वारा ट्रिमटाइज़ को कॉल करने के लिए आवश्यक आइटमों की संख्या के साथ एक सूची है


0

के साथ मेरे अनुभव के अनुसार ArrayList, एक प्रारंभिक क्षमता दे रहा है reallocation लागत से बचने के लिए एक अच्छा तरीका है। लेकिन यह एक चेतावनी है। ऊपर उल्लिखित सभी सुझावों में कहा गया है कि किसी को प्रारंभिक क्षमता तभी प्रदान करनी चाहिए जब तत्वों की संख्या का मोटा अनुमान हो। लेकिन जब हम बिना किसी विचार के एक प्रारंभिक क्षमता देने की कोशिश करते हैं, तो आरक्षित और अप्रयुक्त मेमोरी की मात्रा एक बेकार हो जाएगी क्योंकि सूची के तत्वों की आवश्यक संख्या से भर जाने के बाद इसकी आवश्यकता कभी नहीं हो सकती है। मैं जो कह रहा हूं, हम क्षमता को आवंटित करते समय शुरुआत में व्यावहारिक हो सकते हैं, और फिर रनटाइम में आवश्यक न्यूनतम क्षमता जानने का एक स्मार्ट तरीका खोज सकते हैं। ArrayList नामक एक विधि प्रदान करता है ensureCapacity(int minCapacity)। लेकिन फिर, एक स्मार्ट तरीका मिल गया है ...


0

मैंने ArrayList को बिना शुरुआती और बिना जांचे-परखे परख लिया है और
जब मुझे LOOP_NUMBER को 100,000 या उससे कम पर सेट किया जाता है तो मुझे बहुत ही बढ़िया परिणाम मिलता है।

list1Sttop-list1Start = 14
list2Sttop-list2Start = 10


लेकिन जब मैं LOOP_NUMBER को 1,000,000 में सेट करता हूं तो परिणाम में परिवर्तन होता है:

list1Stop-list1Start = 40
list2Stop-list2Start = 66


अंत में, मैं यह पता नहीं लगा सका कि यह कैसे काम करता है ?!
नमूना कोड:

 public static final int LOOP_NUMBER = 100000;

public static void main(String[] args) {

    long list1Start = System.currentTimeMillis();
    List<Integer> list1 = new ArrayList();
    for (int i = 0; i < LOOP_NUMBER; i++) {
        list1.add(i);
    }
    long list1Stop = System.currentTimeMillis();
    System.out.println("list1Stop-list1Start = " + String.valueOf(list1Stop - list1Start));

    long list2Start = System.currentTimeMillis();
    List<Integer> list2 = new ArrayList(LOOP_NUMBER);
    for (int i = 0; i < LOOP_NUMBER; i++) {
        list2.add(i);
    }
    long list2Stop = System.currentTimeMillis();
    System.out.println("list2Stop-list2Start = " + String.valueOf(list2Stop - list2Start));
}

मैंने windows8.1 और jdk1.7.0_80 पर परीक्षण किया है


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