map遍歷進(jìn)行修改的一些問題

對map,list等進(jìn)行遍歷的時(shí)候,不做增加闭树,刪除的時(shí)候,fori,fore,iterator等方式都沒太大區(qū)別荒澡,當(dāng)需要在遍歷的過程中报辱,增加或刪除元素的時(shí)候,就會(huì)遇到異常了单山。
具體參考這篇文章:
在foreach循環(huán)里進(jìn)行remove/add操作
具體的remove/add參考上面的文章就好碍现,接下來說一下我最近做的蠢事。

首先說一下錯(cuò)誤的做法米奸,在增強(qiáng)for循環(huán)里昼接,進(jìn)行add,和remove操作悴晰,會(huì)導(dǎo)致modCount和expectCount這2個(gè)值不一致慢睡。

我在用迭代器遍歷Map的時(shí)候,先iterator.remove,然后map.put最后拋了異常铡溪,一開始心想漂辐,用了迭代器進(jìn)行remove操作了,為什么還會(huì)拋這個(gè)異常呢棕硫,后來拍了拍腦袋髓涯,先remove,再add,也破壞了modCount和expectCount這2個(gè)值饲帅。

最后我用了2個(gè)數(shù)組复凳,保存了迭代過程中產(chǎn)生的局部遍量瘤泪,在退出循環(huán)之后灶泵,從2個(gè)數(shù)組取值育八,然后map.put,當(dāng)然赦邻,最后數(shù)組取值的 時(shí)候又做了點(diǎn)蠢事髓棋,數(shù)組下標(biāo)越界了。

+++++++++++++++++++++++我是分界線+++++++++++++++++++++++++
評論區(qū)有疑問惶洲,那就再補(bǔ)充一下細(xì)節(jié)按声。

貼代碼

測試代碼

先使用HashMap進(jìn)行測試

public static void main(String[] args) {
        Map<String, Object> map = new HashMap<>();
        map.put("x",new Date());
        map.put("y",new Date());
        Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Object> next = iterator.next();
            System.out.println(next.getKey());
            map.put("z",new Date());
        }
 }

image.png

根據(jù)控制臺(tái)測試結(jié)果,在輸出了第一個(gè)key x之后恬吕,繼續(xù)執(zhí)行的時(shí)候拋出了java.util.ConcurrentModificationException異常签则。

我們查看異常堆棧,拋出異常的地方是java.util.HashMap.HashIterator#nextNode,1445行

image.png

根據(jù)代碼铐料,拋出異常的原因是modCount != expectedModCount渐裂。

這一行代碼說明拋異常的原因就是2個(gè)count不相等造成的

變量解釋

來看一下modCount和expectedModCount具體代表什么意思钠惩。

modCount是HashMap的一個(gè)成員變量柒凉,用來表示HashMap被修改的次數(shù)


image.png

modCount值修改的時(shí)候,是在做put/remove等操作的時(shí)候


image.png

expectedModCount是HashMap的內(nèi)部類java.util.HashMap.HashIterator的一個(gè)成員變量篓跛,初始化值為modCount

image.png

expectedModCount值修改的時(shí)候膝捞,除了初始化,只有一處調(diào)用:java.util.HashMap.HashIterator#remove

image.png

原因分析

前面看了代碼了愧沟,知道異常拋出的原因是modCount != expectedModCount蔬咬。

看代碼,是使用迭代器遍歷map沐寺,在迭代的過程中執(zhí)行了put操作计盒,根據(jù)代碼可知,put操作是會(huì)改變modCount的值的芽丹,但put操作不會(huì)修改expectedModCount北启,expectedModCount的值只有在調(diào)用HashIterator#remove方法的時(shí)候才會(huì)被修改。

所以只修改了一個(gè)值拔第,在執(zhí)行完一次put操作咕村,進(jìn)入下一個(gè)循環(huán)的時(shí)候就會(huì)拋出異常。

安全容器

評論區(qū)也提出了使用安全的容器不會(huì)有這個(gè)異常蚊俺。確實(shí)是這樣懈涛,將HashMap換成ConcurrentHashMap,不會(huì)拋出這個(gè)異常泳猬。

可以看到代碼完整的執(zhí)行完了批钠,并沒有拋出異常宇植。


image.png

關(guān)于安全容器,這里解釋2個(gè)概念

fail-fast

fail-fast埋心,即快速失敗指郁,它是Java集合的一種錯(cuò)誤檢測機(jī)制。當(dāng)多個(gè)線程對集合(非fail-safe的集合類)進(jìn)行結(jié)構(gòu)上的改變的操作時(shí)拷呆,有可能會(huì)產(chǎn)生fail-fast機(jī)制闲坎,這個(gè)時(shí)候就會(huì)拋出ConcurrentModificationException(當(dāng)方法檢測到對象的并發(fā)修改,但不允許這種修改時(shí)就拋出該異常)茬斧。

同時(shí)需要注意的是腰懂,即使不是多線程環(huán)境,如果單線程違反了規(guī)則项秉,同樣也有可能會(huì)拋出改異常绣溜。

HashMap不是并發(fā)安全的容器,所以采用的是fail-fast機(jī)制娄蔼。

剛才在看迭代器的源碼的時(shí)候怖喻,expectedModCount的注釋就寫了這個(gè)變量是為了fast-fail準(zhǔn)備的。


image.png

fail-safe

在Java中贷屎,除了一些普通的集合類以外罢防,還有一些采用了fail-safe機(jī)制的集合類。這樣的集合容器在遍歷時(shí)不是直接在集合內(nèi)容上訪問的唉侄,而是先復(fù)制原有集合內(nèi)容咒吐,在拷貝的集合上進(jìn)行遍歷。

由于迭代時(shí)是對原集合的拷貝進(jìn)行遍歷属划,所以在遍歷過程中對原集合所作的修改并不能被迭代器檢測到恬叹,所以不會(huì)觸發(fā)ConcurrentModificationException。

基于拷貝內(nèi)容的優(yōu)點(diǎn)是避免了ConcurrentModificationException同眯,但同樣地绽昼,迭代器并不能訪問到修改后的內(nèi)容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝须蜗,在遍歷期間原集合發(fā)生的修改迭代器是不知道的硅确。

java.util.concurrent包下的容器都是安全失敗,可以在多線程下并發(fā)使用明肮,并發(fā)修改菱农。

java.util.concurrent.ConcurrentHashMap是符合fail-safe語意的,但是并不是迭代是拷貝副本柿估,創(chuàng)建EntrySet的時(shí)候循未,傳入的是this,即修改的是自身集合


image.png

注釋也作了說明,map的修改會(huì)反映到Set上

  • The set is backed by the map, so changes to the map are *reflected in the set, and vice-versa

關(guān)于ConcurrentHashMap是否存在拷貝秫舌,stackoverflow上有一些問答的妖,供參考

image.png

總結(jié)

Java集合類設(shè)計(jì)時(shí)有2種保障機(jī)制绣檬,為不同場景下安全的使用集合提供了充分的保障。

我們開發(fā)時(shí)一般使用HashMap會(huì)更多嫂粟,需要并發(fā)集合的時(shí)候才會(huì)考慮使用ConcurrentHashMap娇未。

了解2種機(jī)制,可以更好的在實(shí)際開發(fā)過程中進(jìn)行應(yīng)用赋元。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末忘蟹,一起剝皮案震驚了整個(gè)濱河市飒房,隨后出現(xiàn)的幾起案子搁凸,更是在濱河造成了極大的恐慌,老刑警劉巖狠毯,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件护糖,死亡現(xiàn)場離奇詭異,居然都是意外死亡嚼松,警方通過查閱死者的電腦和手機(jī)嫡良,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來献酗,“玉大人寝受,你說我怎么就攤上這事『辟耍” “怎么了很澄?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長颜及。 經(jīng)常有香客問我甩苛,道長,這世上最難降的妖魔是什么俏站? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任讯蒲,我火速辦了婚禮,結(jié)果婚禮上肄扎,老公的妹妹穿的比我還像新娘墨林。我一直安慰自己,他們只是感情好犯祠,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布旭等。 她就那樣靜靜地躺著,像睡著了一般雷则。 火紅的嫁衣襯著肌膚如雪辆雾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天月劈,我揣著相機(jī)與錄音度迂,去河邊找鬼藤乙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛惭墓,可吹牛的內(nèi)容都是我干的坛梁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼腊凶,長吁一口氣:“原來是場噩夢啊……” “哼划咐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钧萍,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤褐缠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后风瘦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體队魏,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年万搔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胡桨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞬雹,死狀恐怖昧谊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酗捌,我是刑警寧澤呢诬,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站意敛,受9級特大地震影響馅巷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜草姻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一钓猬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撩独,春花似錦敞曹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至剧劝,卻和暖如春橄登,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工拢锹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谣妻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓卒稳,卻偏偏與公主長得像蹋半,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子充坑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容