通过一个实际案例,彻底搞懂 HashMap!
我知道大家都很熟悉hashmap,并且有事沒事都會new一個,但是hashmap的一些特性大家都是看了忘,忘了再記,今天這個例子可以幫助大家很好的記住。
場景
用戶提交一張試卷答案到服務端,post報文可精簡為
[{"question\_id":"100001","answer":"A"}, {"question\_id":"100002","answer":"A"}, {"question\_id":"100003","answer":"A"}, {"question\_id":"100004","answer":"A"}]提交地址采用restful風格
http://localhost:8080/exam/{試卷id}/answer那么如何比對客戶端傳過來的題目就是這張試卷里的呢,假設用戶偽造了試卷怎么辦?
正常解決思路
1、得到試卷所有題目id的list
2、2層for循環比對題號和答案
3、判定分數
大概代碼如下
//讀取post題目 for?(MexamTestpaperQuestion?mexamTestpaperQuestion?:?mexamTestpaperQuestions)?{//通過考試試卷讀取題目選項對象MexamQuestionOption?questionOption?=?mexamQuestionDao.findById(mexamTestpaperQuestion.getQuestionId());map1.put("questionid",?mexamTestpaperQuestion.getQuestionId());map1.put("answer",?mexamQuestionDao.findById(mexamTestpaperQuestion.getQuestionId()).getAnswer());questionAnswerList.add(map1);//將每題分add到一個List }//遍歷試卷內所有題目 for?(Map<String,?Object>?stringObjectMap?:?list)?{//生成每題結果對象mexamAnswerInfo?=?new?MexamAnswerInfo();mexamAnswerInfo.setAnswerId(answerId);mexamAnswerInfo.setId(id);mexamAnswerInfo.setQuestionId(questionid);mexamAnswerInfo.setResult(anwser);for?(Map<String,?Object>?objectMap?:?questionAnswerList)?{if?(objectMap.get("questionid").equals(questionid))?{//比較答案if?(anwser.equals(objectMap.get("answer")))?{totalScore?+=?questionOption.getScore();mexamAnswerInfo.setIsfalse(true);}?else?{mexamAnswerInfo.setIsfalse(false);}}}mexamAnswerInfoDao.addEntity(mexamAnswerInfo); }使用普通的2層for循環解決了這個問題,一層是數據庫里的題目,一層是用戶提交的題目,這時候bug就會暴露出來,假設用戶偽造了1萬道題目或更多,服務端運算量將增大很多。
利用hashmap來解決
首先,看看它的定義
基于哈希表的 Map 接口的實現。此實現提供所有可選的映射操作,并允許使用 null 值和 null 鍵。(除了不同步和允許使用 null 之外,HashMap 類與 Hashtable?大致相同。)此類不保證映射的順序,特別是它不保證該順序恒久不變。
主要看HashMap?k-v均支持空值,我們何不將用戶提交了答案add到一個HashMap里,其中題目id作為key,答案作為value,而且HashMap的key支持以字母開頭。
我們只需要for循環試卷所有題目,然后通過這個map.put("題目id")就能得到答案,然后比較答案即可,因為HashMap的key是基于hashcode的形式存儲的,所以在程序中該方案效率很高。
思路:
1、將提交答案以questionid為key,answer為value加入一個hashmap
2、for循環實體列表,直接比對答案
3、判分
代碼如下:
//拿到用戶提交的數據 Map<String,?String>?resultMap?=?new?HashMap<>();JSONArray?questions?=?JSON.parseArray(params.get("questions").toString()); for?(int?size?=?questions.size();?size?>?0;?size--)?{JSONObject?question?=?(JSONObject)?questions.get(size?-?1);resultMap.put(question.getString("questionid"),?question.getString("answer")); } //拿到試卷下的所有試題 List<MexamTestpaperQuestion>?mexamTestpaperQuestions?=?mexamTestpaperQuestionDao.findBy(map); int?totalScore?=?0; for?(MexamTestpaperQuestion?mexamTestpaperQuestion?:?mexamTestpaperQuestions)?{MexamQuestionOption?questionOption?=?mexamQuestionDao.findById(mexamTestpaperQuestion.getQuestionId());MexamAnswerInfo?mexamAnswerInfo?=?new?MexamAnswerInfo();mexamAnswerInfo.setAnswerId(answerId);mexamAnswerInfo.setId(id);mexamAnswerInfo.setQuestionId(questionOption.getId());mexamAnswerInfo.setResult(resultMap.get(questionOption.getId()));//拿到試卷的id作為resultMap的key去查,能查到就有這個題目,然后比對answer,進行存儲if?(questionOption.getAnswer().equals(resultMap.get(questionOption.getId())))?{mexamAnswerInfo.setIsfalse(true);totalScore?+=?questionOption.getScore();}?else?{mexamAnswerInfo.setIsfalse(false);}mexamAnswerInfoDao.addEntity(mexamAnswerInfo); }分析HashMap??
先看看文檔
大概翻譯為如下幾點:
1、實現Map ,可克隆,可序列化
2、基于哈希表的Map接口實現。
3、此實現提供所有可選的映射操作,并允許 空值和空鍵。(HashMap?類大致相當于Hashtable,除非它是不同步的,并且允許null)。這個類不能保證Map的順序; 特別是不能保證訂單在一段時間內保持不變。
4、這個實現為基本操作(get和put)提供了恒定時間的性能,假設散列函數在這些存儲桶之間正確分散元素。集合視圖的迭代需要與HashMap實例的“容量” (桶數)及其大小(鍵值映射數)成正比 。因此,如果迭代性能很重要,不要將初始容量設置得太高(或負載因子太低)是非常重要的。
5、HashMap的一個實例有兩個影響其性能的參數:初始容量和負載因子。容量是在哈希表中桶的數量,和初始容量是簡單地在創建哈希表中的時間的能力。該負載系數是的哈希表是如何充分允許獲得之前它的容量自動增加的措施。當在散列表中的條目的數量超過了負載因數和電流容量的乘積,哈希表被重新散列(即,內部數據結構被重建),使得哈希表具有桶的大約兩倍。
那么put邏輯是怎么樣的呢?
HashMap的key在put時,并不需要挨個使用equals比較,那樣時間復雜度O(n),也就說 HashMap 內有多少元素就需要循環多少次。
而HashMap是將key轉為hashcode,關于hashcode的確可能存在多個string相同的hashcode,但是最終HashMap還會比較一次bucketIndex。bucketIndex是HashMap存儲k-v的位置,時間復雜度只有O(1)。
源碼??
?
/***?Associates?the?specified?value?with?the?specified?key?in?this?map.*?If?the?map?previously?contained?a?mapping?for?the?key,?the?old*?value?is?replaced.**?@param?key?key?with?which?the?specified?value?is?to?be?associated*?@param?value?value?to?be?associated?with?the?specified?key*?@return?the?previous?value?associated?with?<tt>key</tt>,?or*?????????<tt>null</tt>?if?there?was?no?mapping?for?<tt>key</tt>.*?????????(A?<tt>null</tt>?return?can?also?indicate?that?the?map*?????????previously?associated?<tt>null</tt>?with?<tt>key</tt>.)*/ public?V?put(K?key,?V?value)?{//?以key的哈希碼作為key??return?putVal(hash(key),?key,?value,?false,?true); }/***?Implements?Map.put?and?related?methods**?@param?hash?hash?for?key*?@param?key?the?key*?@param?value?the?value?to?put*?@param?onlyIfAbsent?if?true,?don't?change?existing?value*?@param?evict?if?false,?the?table?is?in?creation?mode.*?@return?previous?value,?or?null?if?none*/ final?V?putVal(int?hash,?K?key,?V?value,?boolean?onlyIfAbsent,boolean?evict)?{Node<K,V>[]?tab;?Node<K,V>?p;?int?n,?i;//?處理key為null,HashMap允許key和value為null?if?((tab?=?table)?==?null?||?(n?=?tab.length)?==?0)n?=?(tab?=?resize()).length;if?((p?=?tab[i?=?(n?-?1)?&?hash])?==?null)tab[i]?=?newNode(hash,?key,?value,?null);else?{Node<K,V>?e;?K?k;if?(p.hash?==?hash?&&((k?=?p.key)?==?key?||?(key?!=?null?&&?key.equals(k))))e?=?p;else?if?(p?instanceof?TreeNode)//以樹形結構存儲e?=?((TreeNode<K,V>)p).putTreeVal(this,?tab,?hash,?key,?value);else?{//以鏈表形式存儲for?(int?binCount?=?0;?;?++binCount)?{if?((e?=?p.next)?==?null)?{p.next?=?newNode(hash,?key,?value,?null);if?(binCount?>=?TREEIFY_THRESHOLD?-?1)?//?-1?for?1sttreeifyBin(tab,?hash);break;}if?(e.hash?==?hash?&&((k?=?e.key)?==?key?||?(key?!=?null?&&?key.equals(k))))break;p?=?e;}}//如果是key已存在則修改舊值,并返回舊值if?(e?!=?null)?{?//?existing?mapping?for?keyV?oldValue?=?e.value;if?(!onlyIfAbsent?||?oldValue?==?null)e.value?=?value;afterNodeAccess(e);return?oldValue;}}++modCount;if?(++size?>?threshold)resize();//如果key不存在,則執行插入操作,返回null。afterNodeInsertion(evict);return?null; }put方法分兩種情況:bucket是以鏈表形式存儲的還是以樹形結構存儲的。如果是key已存在則修改舊值,并返回舊值。如果key不存在,則執行插入操作,返回null。put操作,當發生碰撞時,如果是使用鏈表處理沖突,則執行的尾插法。
put操作的大概流程:
1、通過hash值得到所在bucket的下標,如果為null,表示沒有發生碰撞,則直接put。
2、如果發生了碰撞,則解決發生碰撞的實現方式:鏈表還是樹。
3、如果能夠找到該key的結點,則執行更新操作。
4、如果沒有找到該key的結點,則執行插入操作,需要對modCount++。
5、在執行插入操作之后,如果size超過了threshold,這要擴容執行resize()。
作者:邵磊
juejin.im/post/59bc601ff265da0652708abd??
總結
以上是生活随笔為你收集整理的通过一个实际案例,彻底搞懂 HashMap!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图解Spring循环依赖,看过之后再也不
- 下一篇: 面试官给我挖坑:rm删除文件之后,空间就