摘要:迭代器智能嗎第一步,將列表中的根節點找出來。源碼翻開中迭代器的源碼。在迭代器對象執行操作之前,都會執行方法,以判斷當前操作下是否安全。
引言
ConcurrentModificationException這個異常大家都很熟悉,當在forEach進行刪除時都會出現該異常。
如果你還不了解,請參考澍澍的博客:關于在list循環的過程中進行刪除的處理 - 晨澍的博客
ConcurrentModificationException的解決方案之一是使用迭代器,但是不代表迭代器就一勞永逸了。
使用的時候還需斟酌數組的索引。
描述 問題場景如下圖所示:
原來的同步方法獲取的節點是節點的父節點,根據父節點進行對應。
然而在同步更新文件的時候,發現這樣并不好處理,在不改動原代碼的情況下,設計了將列表轉為樹型結構的方法,這樣可以從根節點向下開始遍歷,便于操作。
也是在牛客網身經百戰,實現這個難度不大。但在編寫相關實現的時候,遇到了一個小問題。
迭代器智能嗎?第一步,將列表中的根節點找出來。
@Override public ClusterNode listToTree(ListclusterNodes) { logger.debug("聲明根節點名稱"); final String ROOT_NAME = "ROOT"; logger.debug("聲明根節點"); ClusterNode rootNode = null; logger.debug("獲取迭代器,遍歷節點列表"); Iterator iterator = clusterNodes.iterator(); while (iterator.hasNext()) { logger.debug("向后遍歷"); ClusterNode clusterNode = iterator.next(); if (ROOT_NAME.equals(clusterNode.getName())) { logger.debug("獲取到根節點,賦值,并從原列表中移除"); rootNode = clusterNode; iterator.remove(); break; } } logger.debug("設置子節點"); assert rootNode != null; setChildrenNode(rootNode, clusterNodes); return rootNode; }
第二步,再從根節點開始,遞歸設置子節點。
/** * 為節點設置符合條件的子節點,同時遞歸,設置子節點的子節點 * @param parentNode 父節點 * @param clusterNodes 子節點列表 */ private void setChildrenNode(ClusterNode parentNode, ListclusterNodes) { logger.debug("清空原集合"); parentNode.getClusterNodes().clear(); logger.debug("遍歷列表"); Iterator iterator = clusterNodes.iterator(); while (iterator.hasNext()) { ClusterNode clusterNode = iterator.next(); logger.debug("如果父節點匹配"); if (clusterNode.getParentClusterNode().getName().equals(parentNode.getName())) { logger.debug("將當前節點添加到父節點的子列表中"); parentNode.getClusterNodes().add(clusterNode); logger.debug("移除該節點"); iterator.remove(); logger.debug("遞歸設置子節點"); setChildrenNode(clusterNode, clusterNodes); } } }
思想肯定是沒問題的。
調用了一行遞歸,當我在編寫這行代碼的時候就已經察覺到可能不對了,因為本次迭代器還沒有迭代完,條件符合時就去遞歸,然后遞歸又去新建迭代器,遞歸中又可能刪除新元素,再遞歸回來,繼續迭代的結果還正確嗎?
logger.debug("遞歸設置子節點"); setChildrenNode(clusterNode, clusterNodes);
本著完全相信JDK的思想,興許我憂慮的事情,其實大牛們早就幫我解決了呢?雖然感覺可能不對,但還是這樣寫了。想著把測試用例寫得完善一些,錯了再改唄!
測試一測試,果然出錯了!在調用next方法時拋出了ConcurrentModificationException異常,看來,迭代器還沒有智能到如此地步。
源碼翻開ArrayList中迭代器的源碼。(自從上次在慕課網的驅動下,強制自己閱讀了HashMap的源碼后,發現自己對讀源碼沒那么抗拒了。)
在刷過編程題后,終于明白為什么這些家公司都問HashMap源碼,HashMap真的是太重要了,可以在實際業務中大大降低算法的時間復雜度!
/** * Returns an iterator over the elements in this list in proper sequence. * *The returned iterator is fail-fast. * * @return an iterator over the elements in this list in proper sequence */ public Iterator
iterator() { return new Itr(); }
迭代器方法內部就返回了一個Itr的對象,這是ArrayList中的私有內部類,為什么要用內部類呢?一大好處就是內部類可以直接訪問外部類的私有成員,具體進行集合操作的時候非常方便。
ConcurrentModificationException,因為十分常見,所以我們常常只關注了這個異常怎么出現的,往往忽略了異常本身。
并發修改異常,最簡單的場景,就是線程不安全的多線程并發場景。
在迭代器對象執行操作之前,都會執行checkForComodification方法,以判斷當前操作下是否安全。就像下面的代碼實現一樣,判斷當前的數量是否是我預期的數量。
如果不是,表示有人動過我的列表,拋異常,不干了。
如果是,表示沒有人動過我的列表,才繼續執行。
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }解決方案
既然出現了問題,怎么解決呢?
那就不要在遍歷的過程中再去遞歸修改了,等他遍歷完再說。添加一個待處理的列表,遍歷之后再遞歸,保證每次迭代都不沖突。
logger.debug("下一次需要處理的父節點"); List總結nextParentNodes = new ArrayList<>(); logger.debug("遍歷列表"); Iterator iterator = clusterNodes.iterator(); while (iterator.hasNext()) { ClusterNode clusterNode = iterator.next(); logger.debug("如果父節點匹配"); if (clusterNode.getParentClusterNode().getName().equals(parentNode.getName())) { logger.debug("將當前節點添加到父節點的子列表中"); parentNode.getClusterNodes().add(clusterNode); logger.debug("移除該節點"); iterator.remove(); logger.debug("添加到下一次父節點中"); nextParentNodes.add(clusterNode); } } logger.debug("遞歸處理所有待處理的父節點"); for (ClusterNode clusterNode : nextParentNodes) { setChildrenNode(clusterNode, clusterNodes); }
迭代器并非一勞永逸,保證多次修改的獨立才是最佳實踐。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/77903.html
摘要:迭代器智能嗎第一步,將列表中的根節點找出來。源碼翻開中迭代器的源碼。在迭代器對象執行操作之前,都會執行方法,以判斷當前操作下是否安全。 引言 ConcurrentModificationException這個異常大家都很熟悉,當在forEach進行刪除時都會出現該異常。 如果你還不了解,請參考澍澍的博客:關于在list循環的過程中進行刪除的處理 - 晨澍的博客 showImg(http...
摘要:如果需要創建對象,則必須與一個被迭代的集合。這是一個有狀態的方法該方法用于保證對該流的后續訪問中最大允許訪問的元素個數。可以對集合元素進行整體的聚集操作。 Java集合分為Set(無序、不可重復)、List(有序、重復)、Queue(隊列)和Map(映射關系) Java集合概述 數組元素既可以是基本類型的值,也可以是對象(實際保存對象的引用變量)集合只能保存對象(實際保存對象的引用變量...
摘要:與在迭代器中的設計在中,最典型的與就是關于迭代器的設計。缺點是,迭代器不能正確及時的反應集合中的內容,而且一定程度上也增加了內存的消耗。 fail-fast與fail-safe簡介 如果一個系統,當有異常或者錯誤發生時就立即中斷執行,這種設計稱之為fail-fast。相反如果我們的系統可以在某種異常或者錯誤發生時繼續執行,不會被中斷,這種設計稱之為fail-safe。 fail-fas...
摘要:集合的元素個數為輸出集合的元素個數為在本代碼中,新建一個局部變量保存的成員方法返回的值,輸出得到因為只有一個元素。注若遍歷集合的同時改變集合,將引發異常。 ????在概述里面也說過:Collection是java集合兩大接口之一,旗下有三大子接口:Set(元素不能重復,且無序)、Queue、List(元素可重復,且有序)。????Collection來源于java.util包,主要方法...
閱讀 750·2021-07-25 21:37
閱讀 3663·2019-08-30 15:55
閱讀 2579·2019-08-30 15:54
閱讀 1728·2019-08-30 15:44
閱讀 3129·2019-08-30 15:44
閱讀 866·2019-08-30 15:43
閱讀 1035·2019-08-29 15:36
閱讀 3046·2019-08-29 10:58