java map按照value排序_基础:Java集合需要注意的 5 个问题
生活随笔
收集整理的這篇文章主要介紹了
java map按照value排序_基础:Java集合需要注意的 5 个问题
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
點擊上方?Java后端,選擇?設為星標
優(yōu)質文章,及時送達
Java集合中的List、Set和Map作為Java集合食物鏈的頂級,可謂是各有千秋。本文將對于List、Set和Map之間的聯(lián)系與區(qū)別進行介紹,以及這三者衍生出來的問題進行介紹(若無特地說明,jdk版本皆為1.8):
- List、Set和Map的聯(lián)系和區(qū)別是什么?
- List、Set和Map的使用場景有哪些?
- List與Set之間的怎么轉換?
- Set是怎么保證元素不重復?
- 如何在遍歷的同時刪除ArrayList中的元素?
1. List、Set和Map的聯(lián)系和區(qū)別
List和Set是Collection的實現(xiàn)類,而Map與Collection是屬于“同級“。List
List的特性:- List允許插入重復元素;
- List允許插入多個null元素;
- List作為有序集合,保證了元素按照插入的順序進行排列;
- List提供ListIterator迭代器,可以提供雙向訪問的功能;
- List常用的實現(xiàn)類有:可隨意訪問元素的ArrayList、應用于增刪頻繁的LinkedList、利用synchronized關鍵字實現(xiàn)線程安全的Vector等。
Set
Set的特性:- Set不包含重復元素;
- Set只允許一個null元素的存在;
- Set接口較為流行的實現(xiàn)類有:基于HashMap實現(xiàn)的HashSet、實現(xiàn)SortedSet接口且能更具compare()和compareTo()的定義進行排序的TreeSet等。
Map
Map的特性:- 存儲結構是鍵值對,一個鍵對應一個值;
- 不允許包含重復的鍵,Map可能會持有相同的值對象,但鍵對象必須是唯一的;
- 在Map中可以有多個null值,但最多只能有一個null鍵;
- Map不是Collection的子接口或實現(xiàn)類**,Map是跟Collection“同級”的接口**;
- Map中比較流行的實現(xiàn)類是采用散列函數(shù)的HashMap、以及利用紅黑樹實現(xiàn)排序的TreeMap等。
2. List、Set和Map的使用場景
上文我們介紹完了List、Set和Map之間的聯(lián)系和區(qū)別,接下來我們來看下這三者在使用場景上的差異。List
如果經常使用索引來訪問元素,或者是需要能夠按照插入順序進行存儲,List會是不錯的選擇。- 需要使用索引來訪問容器的元素,ArrayList可以提供更快速的訪問(底層是數(shù)組實現(xiàn));
- 需要經常增刪元素,LinkedList則會是最佳的選擇(底層是鏈表實現(xiàn));
- 數(shù)據量不大,并且有線程安全(synchronized關鍵字)的要求,可以選擇Vector;
- 有線程安全(ReentrantLock實現(xiàn))和性能的要求,讀多寫少的情況,CopyOnWriteArrayList會是更好的選擇。
Set
想要保證插入元素的唯一性,可以選擇Set的實現(xiàn)類。- 需要快速查詢元素,可以使用HashSet(采用散列函數(shù));
- 如果有排序元素的需要,可以使用TreeSet(采用紅黑樹的樹結構排序元素);
- 急需要加快查詢速度,還需要按插入順序來存儲數(shù)據,LinkedHashSet是最好的選擇(采用散列函數(shù)的同時,還使用鏈表維護元素的次序)。
Map
如果需要按鍵值對的形式進行數(shù)據存儲,那么Map是正確的選擇。- 需要快速查詢鍵值元素,可以使用HashMap(采用散列函數(shù));
- 如果需要將鍵進行排序,可以使用TreeMap(按照紅黑樹對鍵進行排序);
- 在存儲數(shù)據少,不允許有null值,又有線程安全(synchronized關鍵字)的要求,可以選擇Hashtable(父類是Dictionary);
- 如果需要線程安全(Node+CAS+Synchronized),且有數(shù)據量和性能要求,ConcurrentHashMap是最佳的選擇。
3. List與Set之間的轉換
因為List和Set都實現(xiàn)了Collection接口中的addAll(Collection extends E> c)方法,而且List和Set也提供了Collection extends E> c為參數(shù)的構造函數(shù),所以可以采用構造函數(shù)的形式,完成List和Set的互相轉換。addAll(Collection extends?E> c)方法public?boolean?addAll(Collection extends?E> c) {boolean?modified = false;for?(E e : c)if?(add(e))????????????????modified = true;return?modified;
????}以Set接口的實現(xiàn)類HashSet為例,其提供了Collection extends E> c為參數(shù)的構造函數(shù)。public?HashSet(Collection extends E> c)?{map?= new?HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
????????addAll(c);
????}以List接口的實現(xiàn)類ArrayList為例,也可以看到它提供了Collection extends E> c為參數(shù)的構造函數(shù)。public?ArrayList(Collection?extends E> c) {
????????elementData = c.toArray();
????????.......
????}所以我們可以得到Set與List之間的轉換方式:Setset?= new?HashSet<>(list);
Listlist?= new?ArrayList<>(set);
4. Set是怎么保證元素不重復?
我們以Set接口最流行的實現(xiàn)類HashSet為例,對Set保證元素不重復的原因進行介紹。private?transient?HashMap map;public?boolean?add(E e)?{????//如果return true,則表示不包含此元素??return?map.put(e, PRESENT)==null;
}從上可知,HashSet是依賴HashMap得以實現(xiàn),其中添加的元素作為HashMap的鍵來存儲。所以接下來就是在介紹“HashMap是怎么保證不允許有相同的鍵存在”了。public?V put(K key, V value) {//倒數(shù)第二個參數(shù)為false,表示允許舊值替換//最后一個參數(shù)為true,表示HashMap不處于創(chuàng)建模式return?putVal(hash(key), key, value, false, true);
}在這里,我們可以看到在進行putVal()方法之前,會將key代入hash()方法中進行散列。final V putVal(int?hash, K key, V value, boolean onlyIfAbsent,
???????????????????boolean evict) {
????????Node[] tab; Node p; int?n, i;//如果哈希表為空,調用resize()方法創(chuàng)建一個哈希表,并用n記錄哈希表的長度if?((tab = table) == null?|| (n = tab.length) == 0)
????????????n = (tab = resize()).length;//如果指定參數(shù)hash(key的hashCode()值)在表中沒有對應的桶,即沒有碰撞//(n-1)&hash計算key將被放置的槽位//(n-1)&hash本質上是hash%n,只是位運算更快if?((p = tab[i = (n - 1) & hash]) == null)//如果沒有碰撞,直接將鍵值對插入到map中即可
????????????tab[i] = newNode(hash, key, value, null);else?{ //如果桶中已經存在了元素
????????????Node e; K k;//比較桶中的第一個元素(數(shù)組中的結點)的hash值、key是否相等if?(p.hash == hash &&
????????????????((k = p.key) == key || (key != null?&& key.equals(k))))//如果相等,則將第一個元素p用e來記錄
????????????????e = p;else?if?(p instanceof TreeNode) //當前桶中無該鍵值對,且桶的結構為紅黑樹,則按照紅黑樹結構的規(guī)則插入元素
????????????????e = ((TreeNode)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);//檢查鏈表長度是否達到閾值,達到則將該槽位的節(jié)點組織形式,轉化為紅黑樹if?(binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
????????????????????????????treeifyBin(tab, hash);break;
????????????????????}//鏈表節(jié)點中的元素與put操作控制的元素相同時,不做重復操作,直接跳出程序if?(e.hash == hash &&
????????????????????????((k = e.key) == key || (key != null?&& key.equals(k))))break;
????????????????????p = e;
????????????????}
????????????}// 如果put操作控制的元素的key和hashCode,與已經插入的元素相等時,執(zhí)行以下操作if?(e != null) { // existing mapping for key// oldValue記錄e的value
????????????????V oldValue = e.value;// onlyIfAbsent為false,或舊值為null時,允許替換舊值,否則無需替換if?(!onlyIfAbsent || oldValue == null)
????????????????????e.value?= value;//訪問后回調
????????????????afterNodeAccess(e);//返回舊值return?oldValue;
????????????}
????????}// 更新結構化修改信息
????????++modCount;// 鍵值對數(shù)目如果超過閾值時,執(zhí)行resize()方法if?(++size > threshold)
????????????resize();// 插入后回調
????????afterNodeInsertion(evict);return?null;
????}從以上源碼中我們可以看出,將一個鍵值對放入HashMap時,首先會根據key的hashCode()返回值和HashMap的長度決定該元素的存儲位置,如果兩個key的hash值相同,那么它們的存儲位置相同。如果這兩個key的equals比較返回true,那么新添加的元素newValue就會覆蓋原來的元素oldValue,key不會被覆蓋。當HashSet中的add()方法里,map.put(e, PRESENT) == null為false時,HashSet添加元素失敗。所以如果向HashSet中添加一個已經存在的元素,新添加的元素不會覆蓋原來已有的元素。
5. 如何在遍歷的同時刪除ArrayList中的元素?
平時我們可能會覺得遍歷ArrayList并刪除其中元素是一件很簡單的事情,但其實這個操作很容易出bug,接下來我們一起看下怎么樣繞過這些坑。從后向前遍歷元素
我們先從前向后遍歷的同時,進行刪除元素:public?static?void?main(String[] args){????????Listlist?= new?ArrayList<>();list.add(1);list.add(2);list.add(3);list.add(3);list.add(4);for(int?i=0; i<list.size()-1; i++){if(list.get(i) == 3){list.remove(new?Integer(3));
????????????}
????????}
????????System.out.println(list);
????}運行結果為:[1, 2, 3, 4]造成這個現(xiàn)象的原因,在【Java集合】ArrayList的使用及原理中筆者稍有提及。在于ArrayList執(zhí)行remove()操作時,將既定元素刪除時還把該元素后的所有元素向前移動一位。這就導致了在遍歷[1,2,3,3,4]時,刪除前一個元素“3”后,將其后元素向前移動一位,因下標[2]已經被遍歷過了,所以就遺漏了第二個“3”。對于這個問題,我們只需要換個遍歷的角度即可——從后往前遍歷:for(int?i=list.size()-1; i>=0; i--){if(list.get(i) == 3){list.remove(new?Integer(3));
????}
}運行結果為:[1, 2, 4]從后往前遍歷,在刪除某一元素之后,也不用擔心在遍歷過程中會遺漏元素。
Iterator.remove()
除了上述遍歷方法,還有一種遍歷方式是我們經常使用的——for-each遍歷:for(Integer i : list){if(i == 3){list.remove(new?Integer(3));????}
}運行結果:Exception in?thread "main"?java.util.ConcurrentModificationException
??at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
??at java.util.ArrayList$Itr.next(ArrayList.java:851)
??...我們知道,for-each的遍歷方式其實是Iterator、hashNext()、next()的復合簡化版。當點開ArrayList.checkForComodification()方法可以看到:private?class?Itr?implements?Iterator<E> {
????......final?void?checkForComodification()?{if?(modCount != expectedModCount)throw?new?ConcurrentModificationException();
??}
}這里的modCount是ArrayList的,而expectedModCount是Itr的,所以其實出錯的地方在于,運行ArrayList.remove()方法時改變了modCount,這就打破了原本modCount == expectedModCount之間和平友好的關系,導致報出并發(fā)修改異常。所以在使用迭代器迭代時(顯示或for-each的隱式)不要使用ArrayList.remove(),改為使用Iterator.remove()即可:Iterator i = list.iterator();while(i.hasNext()){
Integer integer?= i.next();if(integer?== 3){
????i.remove();
????}
}參考資料:List、Set、Map的區(qū)別https://www.cnblogs.com/IvesHe/p/6108933.htmlArrayList循環(huán)遍歷并刪除元素的常見陷阱https://www.cnblogs.com/huangjinyong/p/9455163.htmlJava中Set集合是如何實現(xiàn)添加元素保證不重復的?https://www.cnblogs.com/wupeixuan/p/8858816.html最后
-END-
如果看到這里,說明你喜歡這篇文章,請?轉發(fā)、點贊。同時?標星(置頂)本公眾號可以第一時間接受到博文推送。
推薦閱讀
1.?徹底理解 SpringIOC、DI
2.?圖解 Spring 循環(huán)依賴
3.?GitHub 重大更新:在線開發(fā)上線
4.?Spring Boot 整合 Netty(附源碼)
最近整理一份資料《Java技術棧學習手冊》,覆蓋了Java技術、面試題精選、Spring全家桶、Nginx、SSM、微服務、數(shù)據庫、數(shù)據結構、架構等等。獲取方式:點“?在看,關注公眾號?Java后端?并回復?777?領取,更多內容陸續(xù)奉上。
喜歡文章,點個在看?
總結
以上是生活随笔為你收集整理的java map按照value排序_基础:Java集合需要注意的 5 个问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python 依据某几列累加求和_Pyt
- 下一篇: python flask 大文件 下载_