Java集合大整理
此處為整理,更詳細的源碼分析請查閱?JDK源碼分析其他文章。
為了適應csdn的窗口大小,表格嚴重變形了。。。
| | null | 值重復 | 底層實現(xiàn) | 擴容 | 增、刪、迭代 | 包含 | 備注 |
| HashSet | 允許,just 1個 | no | HashMap | 同HashMap | 【add】:調(diào)用HashMap的put方法,put的value傳入偽值static final?Object?PRESENT?=?new?Object(),僅僅為了保持映射關(guān)系;(所有value都是同一個對象) 【remove】:調(diào)map的remove | 有contains, 無get | HashMap中的Key是根據(jù)對象的hashCode() 和 euqals()來判斷是否唯一的。 So:為了保證HashSet中的對象不會出現(xiàn)重復值,在被存放元素的類中必須要重寫hashCode()和equals()這兩個方法。 |
| TreeSet | No, add-null空指針異常 | no | 【TreeMap】, 實現(xiàn)了NavigableMap接口, 一種SortedMap | 樹結(jié)構(gòu),無擴容一說 | add調(diào)用TreeMap的put方法,同樣有PRESENT; remove方法。 | 有contains, 無get | 默認使用元素的自然順序?qū)υ剡M行排序; 可重寫compare方法實現(xiàn)對象的自定義排序。 |
| EnumSet | No, add-null空指針異常 | no | long(數(shù)組)、 位運算 | Enum元素固定,無擴容 | add:數(shù)組賦值; remove:置null | 有contains, 無get | 判斷是否包含null和removenull無異常;remove-null時返回false |
| EnumMap | No, add-null空指針異常; value可以為null | key不重復 | transient數(shù)組 | Enum元素固定,無擴容 | put:數(shù)組賦值; remove:置null | containsKey; containsValue。 | 創(chuàng)建時必須指定key類型; 元素順序為Enum的順序; 迭代時不會拋出ConcurrentModificationException; NULL和null的區(qū)別。 |
| HashMap | key、value均允許null, 但key僅允許1個null。 | key不重復, value可以 | 位桶+鏈表/紅黑樹 | size?>?tab.length*threshold; newCap = oldCap<<1; 新容量:2倍擴容 | put、remove; 迭代時remove拋ConcurrentModificationException;注意正確迭代方式 | containsKey; containsValue。 | |
| LinkedHashMap | 同HashMap | 同HashMap | HashMap+雙向鏈表 | 同HashMap | put、remove; | 注意get模式; contains調(diào)用HashMap的containsKey; containsValue(遍歷鏈表) | 像hashMap一樣用table儲存元素【桶位依舊分散,和HashMap的存放位置相同】,put時直接調(diào)用的是HashMap的put方法。 |
| TreeMap | Key不允許null; value允許。 | 同HashMap | | | | | |
| ArrayList | 允許null,隨意 | 允許重復 | 數(shù)組 | 初始容量10, grow1.5倍 | | contains判斷元素存在 | |
| LinkedList | 同ArrayList | 同ArrayList | 基于鏈表的數(shù)據(jù)結(jié)構(gòu) | | remove只移除第一個; 迭代時remove拋ConcurrentModificationException(有特例,元素個數(shù)<=2); | 有contains,get | |
| ConcurrentHashMap | key、value均不允許,put-null空指針異常; | 同HashMap | HashMap+CAS無鎖算法 | 實際容量>=sizeCtl,則擴容 | 用foreach迭代,Map定義時必須制定key-value類型,否則cant convert | containsKey、 containsValue | |
| | 允許null:HashMap和以其為底層結(jié)構(gòu)的非同步集合;List | ArrayList相關(guān) | | | | | |
有序: 先說明有序的概念:迭代輸出和存入順序一致即為有序(可以理解為先進先出FIFO)(注:Java8支持list逆序迭代,我們討論有序時忽略這個) 不要和TreeSet弄混了,TreeSet所謂的“有序”,指的是內(nèi)部存儲結(jié)構(gòu)有特定的存儲規(guī)則,它默認使用元素的自然順序?qū)υ剡M行排序,卻打亂了元素的存入順序。So,嚴格來講,TreeSet是無序的。 隨機訪問: 即使用[]操作符訪問其中的元素。
EnumMap:
Set keySet?=?enumMap.keySet();
Iterator iteKey?=?keySet.iterator();
while(iteKey.hasNext()){
????Object object?=(Object)?iteKey.next();
????System.out.print(object?+"="+?enumMap.get(object)+"; ");
}
Collection<Object>vals?=enumMap.values(); Set<Entry<Season,Object>>entrySet?=enumMap.entrySet();HashMap:
- 當某個桶中的鍵值對數(shù)量大于8個【9個起】,且桶數(shù)量大于等于64,則將底層實現(xiàn)從鏈表轉(zhuǎn)為紅黑樹 ;
- int threshold; // 新的擴容resize臨界值,當實際大小(容量*填充比)大于臨界值時,會進行2倍擴容;
- key是有可能是null的,并且會在0桶位位置;
- tableSizeFor(int cap) { //計算下次需要調(diào)整大小的擴容resize臨界值;結(jié)果為>=cap的最小2的自然數(shù)冪(64-》64;65-》128)
- ?length為2的整數(shù)冪保證了length - 1 最后一位(二進制表示)為1,從而保證了索引位置index即( hash &length-1)的最后一位同時有為0和為1的可能性,保證了散列的均勻性。length為2的冪保證了按位與最后一位的有效性,使哈希表散列更均勻。
- resize】時【鏈表】的變化:?元素位置在【原位置】或【原位置+oldCap】
- 鏈表轉(zhuǎn)紅黑樹后,【僅在擴容resize時】若樹變短,會恢復為鏈表。
LinkedHashMap:
- remove后再put,集合結(jié)構(gòu)變化:只要未沖突,table不改變(想想put原理就好理解了);但鏈表改變,新元素始終在tail。
- 顯式地指定為access order后【前提】,調(diào)用get()方法,導致對應的entry移動到雙向鏈表的最后位置(tail),但是table未沒變。
- So LinkedHashMap元素有序存放,但并不保證其迭代順序一直不變
- LinkedHashMap的每個bucket都存了這個bucket的before和after,且每個before(after)又存儲了自身的前驅(qū)后繼,直到null。
ArrayList:
- int newCapacity = oldCapacity + (oldCapacity >> 1);//1.5倍(15 >> 1=7) 擴容是大約1.5倍擴容,HashMap則是剛好2倍擴容。
- add(int index, E element);將當前處于該位置的元素(如果有的話)和所有后續(xù)元素向后移動(其索引加 1)【System.arraycopy】。
- trimToSize()去掉預留元素的位置,返回一個新數(shù)組,新數(shù)組不含null,數(shù)組的size和elementData.length相等,以節(jié)省空間。此函數(shù)可避免size很小但elementData.length很大的情況。
- 不建議使用contains(Object o)方法,看源碼就知道了,調(diào)用其內(nèi)置的indexOf方法,for循環(huán)一個個equals,這效率只能呵呵噠了,建議使用hashcode。
- remove: 首先判斷要remove的元素是null還是非null,然后for循環(huán)查找,核心是fastRemove(index)方法。?fastRemove并不返回被移除的元素。??elementData[--size] = null;因為arraycopy方法是將elementData的index+1處開始的元素往前復制,也就是說最后一個數(shù)本該消除,但還在那里,所以需要置空。
- subList方法得到的subList將和原來的list互相影響,不管你改哪一個,另一個都會隨之改變,而且當父list結(jié)構(gòu)改變時,子list會拋ConcurrentModificationException異常。解決方案:List<String> subListNew = new ArrayList(parentList.subList(1, 3));【類似Arrays.asList()方法】
ConcurrentHashMap:
- CAS算法;unsafe.compareAndSwapInt(this, valueOffset, expect, update); ?CAS(Compare And Swap),意思是如果valueOffset位置包含的值與expect值相同,則更新valueOffset位置的值為update,并返回true,否則不更新,返回false。
- 與Java8的HashMap有相通之處,底層依然由“數(shù)組”+鏈表+紅黑樹;
- 底層結(jié)構(gòu)存放的是TreeBin對象,而不是TreeNode對象;
- CAS作為知名無鎖算法,那ConcurrentHashMap就沒用鎖了么?當然不是,hash值相同的鏈表的頭結(jié)點還是會synchronized上鎖。?
private?transient?volatile?int?sizeCtl;
sizeCtl是控制標識符,不同的值表示不同的意義。
- 負數(shù)代表正在進行初始化或擴容操作?
- -1代表正在初始化?
- -N 表示有N-1個線程正在進行擴容操作?
- 正數(shù)或0代表hash表還沒有被初始化,這個數(shù)值表示初始化或下一次進行擴容的大小,類似于擴容閾值。它的值始終是當前ConcurrentHashMap容量的0.75倍,這與loadfactor是對應的。實際容量>=sizeCtl,則擴容。
? ? 并不是我們傳統(tǒng)的包含key-value的節(jié)點,只是一個標志節(jié)點,并且指向nextTable,提供find方法而已。生命周期:僅存活于擴容操作且bin不為null時,一定會出現(xiàn)在每個bin的首位。
3個原子操作(調(diào)用頻率很高)
static?final?<K,V> Node<K,V>?tabAt(Node<K,V>[]?tab,?int?i) {?// 獲取索引i處Node
????return?(Node<K,V>)U.getObjectVolatile(tab, ((long)i?<<?ASHIFT) +?ABASE);
????}
? ??// 利用CAS算法設(shè)置i位置上的Node節(jié)點(將c和table[i]比較,相同則插入v)。
????static?final?<K,V>?boolean?casTabAt(Node<K,V>[]?tab,?int?i,
????????????????????????????????????????Node<K,V>?c, Node<K,V>?v) {
????????return?U.compareAndSwapObject(tab, ((long)i?<<?ASHIFT) +?ABASE,?c,?v);
????}
? ??// 設(shè)置節(jié)點位置的值,僅在上鎖區(qū)被調(diào)用
????static?final?<K,V>?void?setTabAt(Node<K,V>[]?tab,?int?i, Node<K,V>?v) {
????????U.putObjectVolatile(tab, ((long)i?<<?ASHIFT) +?ABASE,?v);
????}
ConcurrentHashMap無鎖多線程擴容,減少擴容時的時間消耗。 transfer擴容操作:單線程構(gòu)建兩倍容量的nextTable;允許多線程復制原table元素到nextTable。- 節(jié)點為空,則插入ForwardingNode;
- 鏈表節(jié)點(fh>=0),分別插入nextTable的i和i+n的位置;【逆序鏈表??】
- TreeBin節(jié)點(fh<0),判斷是否需要untreefi,分別插入nextTable的i和i+n的位置;【逆序樹??】
- finishing時,nextTab賦給table,更新sizeCtl為新容量的0.75倍 ,完成擴容。
以上說的都是單線程,多線程又是如何實現(xiàn)的呢? 遍歷到ForwardingNode節(jié)點((fh = f.hash) == MOVED),說明此節(jié)點被處理過了,直接跳過。這是控制并發(fā)擴容的核心?。由于給節(jié)點上了鎖,只允許當前線程完成此節(jié)點的操作,處理完畢后,將對應值設(shè)為ForwardingNode(fwd),其他線程看到forward,直接向后遍歷。如此便完成了多線程的復制工作,也解決了線程安全問題。
2、 put相關(guān):
理一下put的流程: ①判空:null直接拋空指針異常; ②hash:計算h=key.hashcode;調(diào)用spread計算hash=(h?^(h?>>>16))&?HASH_BITS; ③遍歷table
- 若table為空,則初始化,僅設(shè)置相關(guān)參數(shù);
- @@@計算當前key存放位置,即table的下標i=(n - 1) & hash;
- 若待存放位置為null,casTabAt無鎖插入;
- 若是forwarding nodes(檢測到正在擴容),則helpTransfer(幫助其擴容);
- else(待插入位置非空且不是forward節(jié)點,即碰撞了),將頭節(jié)點上鎖(保證了線程安全):區(qū)分鏈表節(jié)點和樹節(jié)點,分別插入(遇到hash值與key值都與新節(jié)點一致的情況,只需要更新value值即可。否則依次向后遍歷,直到鏈表尾插入這個結(jié)點);
- 若鏈表長度>8,則treeifyBin轉(zhuǎn)樹(Note:若length<64,直接tryPresize,兩倍table.length;不轉(zhuǎn)樹)。
?以下為引用:?
java提高篇(二十)-----集合大家族:http://demo.netfoucs.com/chenssy/article/details/17732841
????6.1、Vector和ArrayList
???????1,vector是線程同步的,所以它也是線程安全的,而arraylist是線程異步的,是不安全的。如果不考慮到線程的安全因素,一般用arraylist效率比較高。
???????2,如果集合中的元素的數(shù)目大于目前集合數(shù)組的長度時,vector增長率為目前數(shù)組長度的100%,而arraylist增長率為目前數(shù)組長度的50%.如過在集合中使用數(shù)據(jù)量比較大的數(shù)據(jù),用vector有一定的優(yōu)勢。
???????3,如果查找一個指定位置的數(shù)據(jù),vector和arraylist使用的時間是相同的,都是0(1),這個時候使用vector和arraylist都可以。而如果移動一個指定位置的數(shù)據(jù)花費的時間為0(n-i)n為總長度,這個時候就應該考慮到使用linklist,因為它移動一個指定位置的數(shù)據(jù)所花費的時間為0(1),而查詢一個指定位置的數(shù)據(jù)時花費的時間為0(i)。
???????ArrayList 和Vector是采用數(shù)組方式存儲數(shù)據(jù),此數(shù)組元素數(shù)大于實際存儲的數(shù)據(jù)以便增加和插入元素,都允許直接序號索引元素,但是插入數(shù)據(jù)要設(shè)計到數(shù)組元素移動等內(nèi)存操作,所以索引數(shù)據(jù)快插入數(shù)據(jù)慢,Vector由于使用了synchronized方法(線程安全)所以性能上比ArrayList要差,LinkedList使用雙向鏈表實現(xiàn)存儲,按序號索引數(shù)據(jù)需要進行向前或向后遍歷,但是插入數(shù)據(jù)時只需要記錄本項的前后項即可,所以插入數(shù)度較快!
??????6.2、Aarraylist和Linkedlist
???????1.ArrayList是實現(xiàn)了基于動態(tài)數(shù)組的數(shù)據(jù)結(jié)構(gòu),LinkedList基于鏈表的數(shù)據(jù)結(jié)構(gòu)。
???????2.對于隨機訪問get和set,ArrayList覺得優(yōu)于LinkedList,因為LinkedList要移動指針。
???????3.對于新增和刪除操作add和remove,LinedList比較占優(yōu)勢,因為ArrayList要移動數(shù)據(jù)。
這一點要看實際情況的。若只對單條數(shù)據(jù)插入或刪除,ArrayList的速度反而優(yōu)于LinkedList。但若是批量隨機的插入刪除數(shù)據(jù),LinkedList的速度大大優(yōu)于ArrayList. 因為ArrayList每插入一條數(shù)據(jù),要移動插入點及之后的所有數(shù)據(jù)。
??????6.3、HashMap與TreeMap
???????1、HashMap通過hashcode對其內(nèi)容進行快速查找,而TreeMap中所有的元素都保持著某種固定的順序,如果你需要得到一個有序的結(jié)果你就應該使用TreeMap(HashMap中元素的排列順序是不固定的)。HashMap中元素的排列順序是不固定的)。
???????2、 HashMap通過hashcode對其內(nèi)容進行快速查找,而TreeMap中所有的元素都保持著某種固定的順序,如果你需要得到一個有序的結(jié)果你就應該使用TreeMap(HashMap中元素的排列順序是不固定的)。集合框架提供兩種常規(guī)的Map實現(xiàn):HashMap和TreeMap (TreeMap實現(xiàn)SortedMap接口)。
???????3、在Map 中插入、刪除和定位元素,HashMap 是最好的選擇。但如果您要按自然順序或自定義順序遍歷鍵,那么TreeMap會更好。使用HashMap要求添加的鍵類明確定義了hashCode()和 equals()的實現(xiàn)。 這個TreeMap沒有調(diào)優(yōu)選項,因為該樹總處于平衡狀態(tài)。
??????6.4、hashtable與hashmap
???????1、歷史原因:Hashtable是基于陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現(xiàn) 。
???????2、同步性:Hashtable是線程安全的,也就是說是同步的,而HashMap是線程序不安全的,不是同步的 。
???????3、值:只有HashMap可以讓你將空值作為一個表的條目的key或value 。
?????? 七、對集合的選擇
??????7.1、對List的選擇
???????1、對于隨機查詢與迭代遍歷操作,數(shù)組比所有的容器都要快。所以在隨機訪問中一般使用ArrayList
???????2、LinkedList使用雙向鏈表對元素的增加和刪除提供了非常好的支持,而ArrayList執(zhí)行增加和刪除元素需要進行元素位移。
???????3、對于Vector而已,我們一般都是避免使用。
???????4、將ArrayList當做首選,畢竟對于集合元素而已我們都是進行遍歷,只有當程序的性能因為List的頻繁插入和刪除而降低時,再考慮LinkedList。
??????7.2、對Set的選擇
???????1、HashSet由于使用HashCode實現(xiàn),所以在某種程度上來說它的性能永遠比TreeSet要好,尤其是進行增加和查找操作。
???????3、雖然TreeSet沒有HashSet性能好,但是由于它可以維持元素的排序,所以它還是存在用武之地的。
總結(jié)
- 上一篇: YII用户注冊和用户登录(五)之进行se
- 下一篇: 【BOOM】一款有趣的Javascrip