1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        移除List中的元素,你的姿勢(shì)對(duì)了嗎?

        共 10310字,需瀏覽 21分鐘

         ·

        2021-04-10 09:50

        之前遇到對(duì)List進(jìn)行遍歷刪除的時(shí)候,出現(xiàn)來(lái)一個(gè)ConcurrentModificationException 異常,可能好多人都知道list遍歷不能直接進(jìn)行刪除操作,但是你可能只是跟我一樣知道結(jié)果,但是不知道為什么不能刪除,或者說(shuō)這個(gè)報(bào)錯(cuò)是如何產(chǎn)生的,那么我們今天就來(lái)研究一下。

         

        一、異常代碼

        我們先看下這段代碼,你有沒(méi)有寫(xiě)過(guò)類似的代碼

        public static void main(String[] args) {

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

          System.out.println("開(kāi)始添加元素 size:" + list.size());

          for (int i = 0; i < 100; i++) {
            list.add(i + 1);
          }

          System.out.println("元素添加結(jié)束 size:" + list.size());

          Iterator<Integer> iterator = list.iterator();

          while (iterator.hasNext()) {
            Integer next = iterator.next();
            if (next % 5 == 0) {
              list.remove(next);
            }
          }
          System.out.println("執(zhí)行結(jié)束 size:" + list.size());
        }

        「毫無(wú)疑問(wèn),執(zhí)行這段代碼之后,必然報(bào)錯(cuò),我們看下報(bào)錯(cuò)信息?!?/strong>
        我們可以通過(guò)錯(cuò)誤信息可以看到,具體的錯(cuò)誤是在checkForComodification 這個(gè)方法產(chǎn)生的。

         

        二、ArrayList源碼分析

        首先我們看下ArrayListiterator這個(gè)方法,通過(guò)源碼可以發(fā)現(xiàn),其實(shí)這個(gè)返回的是ArrayList內(nèi)部類的一個(gè)實(shí)例對(duì)象。

        public Iterator<E> iterator() {
          return new Itr();
        }

        我們看下Itr類的全部實(shí)現(xiàn)。

        private class Itr implements Iterator<E{
          int cursor;       // index of next element to return
          int lastRet = -1// index of last element returned; -1 if no such
          int expectedModCount = modCount;

          Itr() {}

          public boolean hasNext() {
            return cursor != size;
          }

          @SuppressWarnings("unchecked")
          public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
              throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
              throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
          }

          public void remove() {
            if (lastRet < 0)
              throw new IllegalStateException();
            checkForComodification();

            try {
              ArrayList.this.remove(lastRet);
              cursor = lastRet;
              lastRet = -1;
              expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
              throw new ConcurrentModificationException();
            }
          }

          @Override
          @SuppressWarnings("unchecked")
          public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
              return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
              throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
              consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
          }

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

        「參數(shù)說(shuō)明:」
        cursor : 下一次訪問(wèn)的索引;
        lastRet :上一次訪問(wèn)的索引;
        expectedModCount :對(duì)ArrayList修改次數(shù)的期望值,初始值為modCount
        modCount :它是AbstractList的一個(gè)成員變量,表示ArrayList的修改次數(shù),通過(guò)addremove方法可以看出;
        「幾個(gè)常用方法:」
        hasNext():

        public boolean hasNext() {
         return cursor != size;
        }

        如果下一個(gè)訪問(wèn)元素的下標(biāo)不等于size,那么就表示還有元素可以訪問(wèn),如果下一個(gè)訪問(wèn)的元素下標(biāo)等于size,那么表示后面已經(jīng)沒(méi)有可供訪問(wèn)的元素。因?yàn)樽詈笠粋€(gè)元素的下標(biāo)是size()-1,所以當(dāng)訪問(wèn)下標(biāo)等于size的時(shí)候必定沒(méi)有元素可供訪問(wèn)。
        next()

        public E next() {
          checkForComodification();
          int i = cursor;
          if (i >= size)
            throw new NoSuchElementException();
          Object[] elementData = ArrayList.this.elementData;
          if (i >= elementData.length)
            throw new ConcurrentModificationException();
          cursor = i + 1;
          return (E) elementData[lastRet = i];
        }

        注意下,這里面有兩個(gè)非常重要的地方,cursor初始值是0,獲取到元素之后,cursor 加1,那么它就是下次索要訪問(wèn)的下標(biāo),最后一行,將i賦值給了lastRet這個(gè)其實(shí)就是上次訪問(wèn)的下標(biāo)。
        此時(shí),cursor變?yōu)榱?,lastRet變?yōu)榱?。
        最后我們看下ArrayListremove()方法做了什么?

        public boolean remove(Object o) {
          if (o == null) {
            for (int index = 0; index < size; index++)
              if (elementData[index] == null) {
                fastRemove(index);
                return true;
              }
          } else {
            for (int index = 0; index < size; index++)
              if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
              }
          }
          return false;
        }

        private void fastRemove(int index) {
          modCount++;
          int numMoved = size - index - 1;
          if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
          elementData[--size] = null// clear to let GC do its work
        }

        「重點(diǎn):」
        我們先記住這里,modCount初始值是0,刪除一個(gè)元素之后,modCount自增1,接下來(lái)就是刪除元素,最后一行將引用置為null是為了方便垃圾回收器進(jìn)行回收。

         

        三、問(wèn)題定位

        到這里,其實(shí)一個(gè)完整的判斷、獲取、刪除已經(jīng)走完了,此時(shí)我們回憶下各個(gè)變量的值:
        cursor : 1(獲取了一次元素,默認(rèn)值0自增了1);
        lastRet :0(上一個(gè)訪問(wèn)元素的下標(biāo)值);
        expectedModCount :0(初始默認(rèn)值);
        modCount :1(進(jìn)行了一次remove操作,變成了1);
        不知道你還記不記得,next()方法中有兩次檢查,如果已經(jīng)忘記的話,建議你往上翻一翻,我們來(lái)看下這個(gè)判斷:

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

        當(dāng)modCount不等于expectedModCount的時(shí)候拋出異常,那么現(xiàn)在我們可以通過(guò)上面各變量的值發(fā)現(xiàn),兩個(gè)變量的值到底是多少,并且知道它們是怎么演變過(guò)來(lái)的。那么現(xiàn)在我們是不是清楚了ConcurrentModificationException異常產(chǎn)生的愿意呢!
        「就是因?yàn)椋?/strong>list.remove()導(dǎo)致modCountexpectedModCount的值不一致從而引發(fā)的問(wèn)題?!?/strong>

         

        四、解決問(wèn)題

        我們現(xiàn)在知道引發(fā)這個(gè)問(wèn)題,是因?yàn)閮蓚€(gè)變量的值不一致所導(dǎo)致的,那么有沒(méi)有什么辦法可以解決這個(gè)問(wèn)題呢!答案肯定是有的,通過(guò)源碼可以發(fā)現(xiàn),Iterator里面也提供了remove方法。

        public void remove() {
          if (lastRet < 0)
            throw new IllegalStateException();
          checkForComodification();

          try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
          } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
          }
        }

        你看它做了什么,它將modCount的值賦值給了expectedModCount,那么在調(diào)用next()進(jìn)行檢查判斷的時(shí)候勢(shì)必不會(huì)出現(xiàn)問(wèn)題。
        那么以后如果需要remove的話,千萬(wàn)不要使用list.remove()了,而是使用iterator.remove(),這樣其實(shí)就不會(huì)出現(xiàn)異常了。

        public static void main(String[] args) {

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

          System.out.println("開(kāi)始添加元素 size:" + list.size());

          for (int i = 0; i < 100; i++) {
            list.add(i + 1);
          }

          System.out.println("元素添加結(jié)束 size:" + list.size());

          Iterator<Integer> iterator = list.iterator();

          while (iterator.hasNext()) {
            Integer next = iterator.next();
            if (next % 5 == 0) {
              iterator.remove();
            }
          }
          System.out.println("執(zhí)行結(jié)束 size:" + list.size());
        }

        「建議:」
        另外告訴大家,我們?cè)谶M(jìn)行測(cè)試的時(shí)候,如果找不到某個(gè)類的實(shí)現(xiàn)類,因?yàn)橛袝r(shí)候一個(gè)類有超級(jí)多的實(shí)現(xiàn)類,但是你不知道它到底調(diào)用的是哪個(gè),那么你就通過(guò)debug的方式進(jìn)行查找,是很便捷的方法。

         

        五、總結(jié)

        其實(shí)這個(gè)問(wèn)題很常見(jiàn),也是很簡(jiǎn)單,但是我們做技術(shù)的就是把握細(xì)節(jié),通過(guò)追溯它的具體實(shí)現(xiàn),發(fā)現(xiàn)它的問(wèn)題所在,這樣你不僅僅知道這樣有問(wèn)題,而且你還知道這個(gè)問(wèn)題具體是如何產(chǎn)生的,那么今后不論對(duì)于你平時(shí)的工作還是面試都是莫大的幫助。
        本期分享就到這里,謝謝各位看到此處,
        記得點(diǎn)個(gè)贊呦!

        有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)

        歡迎大家關(guān)注Java之道公眾號(hào)


        好文章,我在看??

        瀏覽 33
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美人妻视频 | 女人被舔荫蒂舒服了免费视频 | 男女操比视频 | 国产一区电影 | 国产精品羞羞无码久久久苹果 | 免费看AA片 | 国产视频你懂的 | 揉我胸啊嗯出水了嗯 | 蜜臀AV在线播放 | 日本淫色视频 |