手把手教你錘面試官01——HashMap面試全攻略

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 本文是手把手教你錘面試官系列第一篇文章,該系列主要爲大家分析和講解在面試過程中,遇到面試官經常提出的面試問題如何進行攻防。另外本系列的文章也會提供許多小技巧給大家去間接試探出面試官的技術能力和專業水平,從而達到碾壓面試官的目的。本系列文章只適用於JAVA工程師。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 很多面試官喜歡問HashMap的一個原因就是因爲HashMap我們經常在開發中用到,而且它線程不安全。這裏有幾個隱藏的問題:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":" 0.HashMap的結構是怎麼樣的?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":" 1.HashMap爲什麼線程不安全?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":" 2.HashMap線程不安全在程序中的體現是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":" 3.既然HashMap線程不安全,爲什麼我們還經常用它?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":" 4.怎麼樣可以讓HashMap線程安全?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"0.0回答"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲HashMap的數據結構是散列結構,散列結構就是我們說的key-value結構,它由一個數組和多個鏈表組成。每個數組節點對應一個鏈表。key值是一個數組存放,value的值放在多個鏈表中存放。下圖就是一個典型的散列結構的圖,左邊是一個數組,右邊是每個數組節點對應的鏈表。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1c/1c938ad09b2086962a3dcd7262069a6a.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"1.1回答"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HashMap中put操作的主函數,如果沒有hash碰撞則會直接插入元素。如果線程A和線程B同時進行put操作,剛好這兩條不同的數據hash值一樣,並且該位置數據爲null,所以這線程A、B都會進入下面代碼的第6行代碼中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設一種情況,線程A進入後還未進行數據插入時掛起,而線程B正常執行,從而正常插入數據,然後線程A獲取CPU時間片,此時線程A不用再進行hash判斷了,問題出現:線程A會把線程B插入的數據給覆蓋,發生線程不安全。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"final V putVal(int hash, K key, V value, boolean onlyIfAbsent,\n boolean evict) {\n Node[] tab; Node p; int n, i;\n if ((tab = table) == null || (n = tab.length) == 0)\n n = (tab = resize()).length;\n if ((p = tab[i = (n - 1) & hash]) == null) // 如果沒有hash碰撞則會直接插入元素\n tab[i] = newNode(hash, key, value, null);\n else {\n Node e; K k;\n if (p.hash == hash &&\n ((k = p.key) == key || (key != null && key.equals(k))))\n e = p;\n else if (p instanceof TreeNode)\n e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);\n else {\n for (int binCount = 0; ; ++binCount) {\n if ((e = p.next) == null) {\n p.next = newNode(hash, key, value, null);\n if (binCount >= TREEIFY_THRESHOLD - 1) \n treeifyBin(tab, hash);\n break;\n }\n if (e.hash == hash &&\n ((k = e.key) == key || (key != null && key.equals(k))))\n break;\n p = e;\n }\n }\n if (e != null) { \n V oldValue = e.value;\n if (!onlyIfAbsent || oldValue == null)\n e.value = value;\n afterNodeAccess(e);\n return oldValue;\n }\n }\n ++modCount;\n if (++size > threshold)\n resize();\n afterNodeInsertion(evict);\n return null;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"2.2回答"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":"多線程操作HashMap的場景下,會發生HashMap的值被覆蓋,從而導致數據丟失。既同一個數組對應的鏈表被反覆替換。另外,如果面試官問,會不會出現死循環或者數組下標越界。你就可以盡情的嘲諷,這個問題在JDK1.8已經不會出現了,難道貴公司還用JDK1.8以下版本?(這樣做的好處是避免遇到面試官問你紅黑二叉樹還有HashMap的擴容機制,儘量避開)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"3.3回答"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"廢話,因爲好用嘛。請看下圖,下圖是JAVA官方API中截取出來的類圖。從下面這張圖我們可以看出來Map是一個接口,我們常用的實現類有HashMap、HashTable、LinkedHashMap、TreeMap。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HashTable類是線程安全的,它使用synchronize來做線程安全,全局只有一把鎖,在線程競爭比較激烈的情況下hashtable的效率是比較低下的。因爲當一個線程訪問hashtable的同步方法時,其他線程再次嘗試訪問的時候,會進入阻塞或者輪詢狀態,進而導致線程掛起。當你在多線程場景下使用Map,雖然不能使用HashMap但也儘量別使用HashTable。其實我個人覺得這個類可以廢掉了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"LinkedHashMap就更不用說了,本身也是線程不安全的,而且每次插入的時候都要自己排序一次,每次排序都要進行一次遍歷查詢。你都用map了,你還會在意插入的順序嘛?大多數情況你也不會在意插入的節點順序。除非你一定要記錄Map中節點插入的先後順序,否則使用LinkedHashMap只會消耗性能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TreeMap合LinkedHashMap一樣,線程不安全而且要排序。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/11/1104437a38a96398efa08be2b9b1fd9d.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"4.4回答"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在多線程場景下放棄使用HashMap,可以使用CurrentHashMap替代HashMap。CurrentHashMap是綫程安全的,因爲它和HashTable一樣加了鎖。而且我們上邊也提到了,由於HashTable 使用一把鎖(鎖住整個鏈表結構)處理併發問題,多個線程競爭一把鎖,容易線程阻塞、掛起。而CurrentHashMap採用CAS + synchronized + Node + 紅黑樹的多段分級鎖的機制,可以對鏈表進行非常細粒度的加鎖,不容易造成線程阻塞。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"額外技巧"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":"知己知彼,百戰不殆。在面試過程中如果不知道面試官專業水平深淺的時候,可以在進行回答的時候故意漏出一個比較大的破綻。如果面試官沒有提出異議,則說明水平不高,回答的時候儘量往底層走就可以唬住對方,讓主動權在你手上。比方說回答HashMap的問題時,可以賣一個破綻——HashMap的默認長度是8(其實是16)。如果面試官沒有提出異議,則說明該面試官對於HashMap這塊最多也就是知道它線程不安全,可用CurrentHashMap替代。本文上面粗淺的內容最夠你吊打對方。如果對方指正你默認長度是16,你可以先說,沒錯就是16,剛纔一時緊張口誤了。然後儘量避開對方提問HashMap擴容機制、鏈表紅黑二叉樹轉換等問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章