java - लूप में हटाते समय ConcurrentModificationException से परहेज करते हुए, संग्रह के माध्यम से Iterating




collections (14)

हम सभी जानते हैं कि आप यह नहीं कर सकते हैं:

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}

ConcurrentModificationException आदि ... यह स्पष्ट रूप से कभी-कभी काम करता है, लेकिन हमेशा नहीं। यहां कुछ विशिष्ट कोड दिया गया है:

public static void main(String[] args) {
    Collection<Integer> l = new ArrayList<Integer>();

    for (int i=0; i < 10; ++i) {
        l.add(new Integer(4));
        l.add(new Integer(5));
        l.add(new Integer(6));
    }

    for (Integer i : l) {
        if (i.intValue() == 5) {
            l.remove(i);
        }
    }

    System.out.println(l);
}

यह, ज़ाहिर है, परिणाम:

Exception in thread "main" java.util.ConcurrentModificationException

... भले ही कई धागे इसे नहीं कर रहे हैं ... वैसे भी।

इस समस्या का सबसे अच्छा समाधान क्या है? इस अपवाद को फेंक दिए बिना मैं लूप में संग्रह से किसी आइटम को कैसे हटा सकता हूं?

मैं यहां एक मनमानी Collection का भी उपयोग कर रहा हूं, जरूरी नहीं कि एक ArrayList , इसलिए आप get पर भरोसा नहीं कर सकते।


आप फॉर-लूप का उपयोग करके सूची को पुन: सक्रिय कर सकते हैं और आपको list.remove (0) पर कॉल करने की आवश्यकता है। आपको इंडेक्स को शून्य के साथ इंडेक्स पैरामीटर को हार्ड-कोड करने की आवश्यकता है। यह जवाब भी देखें:

List<Integer> list = new ArrayList<Integer>();

list.add(1);
list.add(2);
list.add(3);
list.add(4);
int list_size = list.size();
for (int i = 0; i < list_size; i++) {
    list.remove(0);
}

आप या तो सीधे उल्लेखित इटरेटर का उपयोग कर सकते हैं, या फिर दूसरा संग्रह रखें और प्रत्येक संग्रह को नए संग्रह में निकालना चाहते हैं, फिर अंत में हटा दें। यह आपको बढ़ी हुई मेमोरी उपयोग और सीपीयू समय की लागत पर प्रत्येक लूप के प्रकार-सुरक्षा का उपयोग जारी रखने की अनुमति देता है (जब तक कि वास्तव में, वास्तव में बड़ी सूचियां या वास्तव में पुराना कंप्यूटर न हो) एक बड़ी समस्या नहीं होनी चाहिए)

public static void main(String[] args)
{
    Collection<Integer> l = new ArrayList<Integer>();
    Collection<Integer> itemsToRemove = new ArrayList<Integer>();
    for (int i=0; i < 10; ++i) {
    l.add(new Integer(4));
    l.add(new Integer(5));
    l.add(new Integer(6));
    }
    for (Integer i : l)
    {
        if (i.intValue() == 5)
            itemsToRemove.add(i);
    }

    l.removeAll(itemsToRemove);
    System.out.println(l);
}

ऐसे मामलों में पीछे की ओर जाने के लिए एक आम चाल है (था?):

for(int i = l.size() - 1; i >= 0; i --) {
  if (l.get(i) == 5) {
    l.remove(i);
  }
}

उस ने कहा, मैं खुश हूं कि आपके पास जावा 8 में बेहतर तरीके हैं, उदाहरण के लिए removeIf या स्ट्रीम पर filter करें।


चूंकि प्रश्न का पहले ही उत्तर दिया गया है यानी इटरेटर ऑब्जेक्ट की निकासी विधि का उपयोग करने का सबसे अच्छा तरीका है, मैं उस स्थान के विनिर्देशों में जाऊंगा जहां त्रुटि "java.util.ConcurrentModificationException" फेंक दी गई है।

प्रत्येक संग्रह वर्ग में एक निजी वर्ग होता है जो इटरेटर इंटरफ़ेस लागू करता है और next() , remove() और hasNext() जैसी विधियां प्रदान करता है।

अगले के लिए कोड इस तरह कुछ दिखता है ...

public E next() {
    checkForComodification();
    try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
    } catch(IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
    }
}

यहां विधि checkForComodification को कार्यान्वित किया गया है

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

इसलिए, जैसा कि आप देख सकते हैं, अगर आप स्पष्ट रूप से संग्रह से तत्व को निकालने का प्रयास करते हैं। इसके परिणामस्वरूप modCount से अलग हो रहा expectedModCount , जिसके परिणामस्वरूप अपवाद ConcurrentModificationException अपवाद है।


मुझे मूर्ख:

Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
    if (iter.next().intValue() == 5) {
        iter.remove();
    }
}

मुझे लगता है कि चूंकि एक फोरैच लूप पुनरावृत्ति के लिए सिंटेक्टिक चीनी है, इसलिए एक इटरेटर का उपयोग करने में मदद नहीं होगी ... लेकिन यह आपको यह देता है .remove() कार्यक्षमता।


मेरे पास उपरोक्त समस्या के लिए एक सुझाव है। माध्यमिक सूची या किसी भी अतिरिक्त समय की कोई ज़रूरत नहीं है। कृपया एक उदाहरण खोजें जो एक ही सामान करेगा लेकिन एक अलग तरीके से।

//"list" is ArrayList<Object>
//"state" is some boolean variable, which when set to true, Object will be removed from the list
int index = 0;
while(index < list.size()) {
    Object r = list.get(index);
    if( state ) {
        list.remove(index);
        index = 0;
        continue;
    }
    index += 1;
}

यह Concurrency अपवाद से बच जाएगा।


यह सबसे अच्छा तरीका नहीं हो सकता है, लेकिन अधिकांश छोटे मामलों के लिए यह स्वीकार्य होना चाहिए:

"एक दूसरा खाली-सरणी बनाएं और केवल वे जिन्हें आप रखना चाहते हैं"

मैं इसे याद नहीं करता हूं जहां से मैंने इसे पढ़ा ... न्याय के लिए मैं इस विकी को उम्मीद में कर दूंगा कि किसी को यह पता चलता है या सिर्फ प्रतिनिधि अर्जित करने के लिए मैं लायक नहीं हूं।


लूप के लिए पारंपरिक के साथ

ArrayList<String> myArray = new ArrayList<>();

   for (int i = 0; i < myArray.size(); ) {
        String text = myArray.get(i);
        if (someCondition(text))
             myArray.remove(i);
        else 
             i++;
      }

ConcurrentHashMap या ConcurrentLinkedQueue या ConcurrentSkipListMap एक और विकल्प हो सकता है, क्योंकि यदि आप आइटम को हटाते या जोड़ते हैं, तो भी वे किसी भी ConcurrentModificationException को फेंक नहीं देंगे।


Iterator.remove() सुरक्षित है, आप इसे इस तरह उपयोग कर सकते हैं:

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

// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
//     Iterator<String> iterator = list.iterator();
//     while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
    String string = iterator.next();
    if (string.isEmpty()) {
        // Remove the current element from the iterator and the list.
        iterator.remove();
    }
}

ध्यान दें कि Iterator.remove() पुनरावृत्ति के दौरान संग्रह को संशोधित करने का एकमात्र सुरक्षित तरीका है; यदि पुनरावृत्ति संग्रह प्रगति पर है, तो अंतर्निहित संग्रह किसी भी अन्य तरीके से संशोधित किया गया है, तो व्यवहार अनिर्दिष्ट है।

स्रोत: docs.oracle.com/javase/tutorial/collections/interfaces/…

और इसी तरह, यदि आपके पास ListIterator और आइटम जोड़ना चाहते हैं , तो आप ListIterator#add उपयोग कर सकते ListIterator#add , इसी कारण से आप Iterator#remove सकते हैं - इसे अनुमति देने के लिए डिज़ाइन किया गया है।


@assylias answer अतिरिक्त यदि आप जावा 8 का उपयोग करते हैं तो आप नए Stream एपीआई का भी उपयोग कर सकते हैं:

List<Integer> l = Arrays.asList(4, 5, 6);

static boolean condition(Integer i) {
    return i == 5;
}

static Predicate<Integer> predicate = YourClassName::condition;

l.stream()
    .filter(predicate.negate())
    .forEach(System.out::println);

यदि आप इस स्थिति को उलटा करते हैं, तो समाधान अधिक संक्षिप्त होता है क्योंकि आपको भविष्यवाणी negate() को negate() करने की आवश्यकता नहीं होती है, इस प्रकार आप केवल विधि संदर्भ का उपयोग करने की अनुमति देते हैं:

List<Integer> l = Arrays.asList(4, 5, 6);

static boolean condition(Integer i) {
    return i != 5;    // <-- condition has been negated
}

l.stream()
    .filter(YourClassName::condition)
    .forEach(System.out::println);

इसकी सुंदरताओं में से एक यह है कि धारा का आलसी मूल्यांकन किया जाता है, यानि filter() ऑपरेशन का वास्तव में मूल्यांकन नहीं किया जाता है जब तक कि इसे टर्मिनल ऑपरेशन जैसे forEach() लिए उपयोग नहीं किया जाता है। इस पर अधिक ओरेकल के Tutorial में पाया जा सकता है।


ArrayList के मामले में : हटाएं (int अनुक्रमणिका) - अगर (सूचकांक अंतिम तत्व की स्थिति है) यह System.arraycopy() बिना से बचाता है और इसके लिए समय नहीं लगता है।

सरणी का समय बढ़ता है अगर (सूचकांक घटता है), जिस तरह से सूची के तत्व भी कम हो जाते हैं!

सबसे प्रभावी प्रभावी तरीका है- अपने तत्वों को अवरोही क्रम में हटा रहा है: while(list.size()>0)list.remove(list.size()-1); // लेता है ओ (1) while(list.size()>0)list.remove(0); // लेता है ओ (फैक्टोरियल (एन))

//region prepare data
ArrayList<Integer> ints = new ArrayList<Integer>();
ArrayList<Integer> toRemove = new ArrayList<Integer>();
Random rdm = new Random();
long millis;
for (int i = 0; i < 100000; i++) {
    Integer integer = rdm.nextInt();
    ints.add(integer);
}
ArrayList<Integer> intsForIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsDescIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsIterator = new ArrayList<Integer>(ints);
//endregion

// region for index
millis = System.currentTimeMillis();
for (int i = 0; i < intsForIndex.size(); i++) 
   if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--);
System.out.println(System.currentTimeMillis() - millis);
// endregion

// region for index desc
millis = System.currentTimeMillis();
for (int i = intsDescIndex.size() - 1; i >= 0; i--) 
   if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i);
System.out.println(System.currentTimeMillis() - millis);
//endregion

// region iterator
millis = System.currentTimeMillis();
for (Iterator<Integer> iterator = intsIterator.iterator(); iterator.hasNext(); )
    if (iterator.next() % 2 == 0) iterator.remove();
System.out.println(System.currentTimeMillis() - millis);
//endregion
  • इंडेक्स लूप के लिए: 10 9 0 एमसीसी
  • desc सूचकांक के लिए: 51 9 एमसीसी --- सबसे अच्छा
  • इटरेटर के लिए: 1043 एमसीसी

List<String> strings=new ArrayList<String>(){};

while(strings.size() > 0) {

 String str = strings.remove(0);
}

for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

यदि आप आंतरिक iterator.next () कॉल को छोड़ते हैं तो सूची से तत्व को हटाने के बाद पकड़ है। यह अभी भी काम करता है! हालांकि मैं इस तरह कोड लिखने का प्रस्ताव नहीं करता हूं, लेकिन इसके पीछे की अवधारणा को समझने में मदद मिलती है :-)

चीयर्स!





collections