Java集合大整理
此處為整理,更詳細的原始碼分析請查閱
為了適應csdn的視窗大小,表格嚴重變形了。。。
null | 值重複 | 底層實現 | 擴容 | 增、刪、迭代 | 包含 | 備註 | |
HashSet | 允許,just 1個 | no | HashMap | 同HashMap | 【add】:呼叫HashMap的put方法,put的value傳入偽值static final Object PRESENT = new Object(),僅僅為了保持對映關係;(所有value都是同一個物件) 【remove】:調map的remove | 有contains, 無get | HashMap中的Key是根據物件的hashCode() 和 euqals()來判斷是否唯一的。 So:為了保證HashSet中的物件不會出現重複值,在被存放元素的類中必須要重寫hashCode()和equals()這兩個方法。 |
TreeSet |
No, add-null空指標異常 |
no | 【TreeMap】, 實現了NavigableMap介面, 一種SortedMap | 樹結構,無擴容一說 | add呼叫TreeMap的put方法,同樣有PRESENT; remove方法。 |
有contains,
無get |
預設使用元素的自然順序對元素進行排序; 可重寫compare方法實現物件的自定義排序。 |
EnumSet |
No, add-null空指標異常 |
no | long(陣列)、 位運算 | Enum元素固定,無擴容 | add:陣列賦值; remove:置null |
有contains, |
判斷是否包含null和removenull無異常;remove-null時返回false |
EnumMap | No, add-null空指標異常; value可以為null | key不重複 | transient陣列 |
Enum元素固定,無擴容 |
put:陣列賦值; remove:置null | containsKey; containsValue。 | 建立時必須指定key型別; 元素順序為Enum的順序; 迭代時不會丟擲ConcurrentModificationException; NULL和null的區別。 |
HashMap |
key、value均允許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呼叫HashMap的containsKey; containsValue(遍歷連結串列) |
像hashMap一樣用table儲存元素【桶位依舊分散,和HashMap的存放位置相同】,put時直接呼叫的是HashMap的put方法。 |
TreeMap | Key不允許null; value允許。 |
同HashMap |
|||||
ArrayList | 允許null,隨意 | 允許重複 | 陣列 | 初始容量10, grow1.5倍 | contains判斷元素存在 | ||
LinkedList | 同ArrayList |
同ArrayList |
基於連結串列的資料結構 | remove只移除第一個; 迭代時remove拋ConcurrentModificationException(有特例,元素個數<=2); |
有contains,get
|
||
ConcurrentHashMap | key、value均不允許,put-null空指標異常; |
同HashMap |
HashMap+CAS無鎖演算法 |
實際容量>=sizeCtl,則擴容 | 用foreach迭代,Map定義時必須制定key-value型別,否則cant convert |
containsKey、
containsValue |
|
允許null:HashMap和以其為底層結構的非同步集合;List |
ArrayList相關 |
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:
- 當某個桶中的鍵值對數量大於8個【9個起】,且桶數量大於等於64,則將底層實現從連結串列轉為紅黑樹 ;
- int threshold; // 新的擴容resize臨界值,當實際大小(容量*填充比)大於臨界值時,會進行2倍擴容;
- key是有可能是null的,並且會在0桶位位置;
- tableSizeFor(int cap) { //計算下次需要調整大小的擴容resize臨界值;結果為>=cap的最小2的自然數冪(64-》64;65-》128)
- length為2的整數冪保證了length - 1 最後一位(二進位制表示)為1,從而保證了索引位置index即( hash &length-1)的最後一位同時有為0和為1的可能性,保證了雜湊的均勻性。length為2的冪保證了按位與最後一位的有效性,使雜湊表雜湊更均勻。
- resize】時【連結串列】的變化: 元素位置在【原位置】或【原位置+oldCap】
- 連結串列轉紅黑樹後,【僅在擴容resize時】若樹變短,會恢復為連結串列。
- remove後再put,集合結構變化:只要未衝突,table不改變(想想put原理就好理解了);但連結串列改變,新元素始終在tail。
- 顯式地指定為access order後【前提】,呼叫get()方法,導致對應的entry移動到雙向連結串列的最後位置(tail),但是table未沒變。
- So LinkedHashMap元素有序存放,但並不保證其迭代順序一直不變
- LinkedHashMap的每個bucket都存了這個bucket的before和after,且每個before(after)又儲存了自身的前驅後繼,直到null。
- int newCapacity = oldCapacity + (oldCapacity >> 1);//1.5倍(15 >> 1=7) 擴容是大約1.5倍擴容,HashMap則是剛好2倍擴容。
- add(int index, E element);將當前處於該位置的元素(如果有的話)和所有後續元素向後移動(其索引加 1)【System.arraycopy】。
- trimToSize()去掉預留元素的位置,返回一個新陣列,新陣列不含null,陣列的size和elementData.length相等,以節省空間。此函式可避免size很小但elementData.length很大的情況。
- 不建議使用contains(Object o)方法,看原始碼就知道了,呼叫其內建的indexOf方法,for迴圈一個個equals,這效率只能呵呵噠了,建議使用hashcode。
- remove: 首先判斷要remove的元素是null還是非null,然後for迴圈查詢,核心是fastRemove(index)方法。 fastRemove並不返回被移除的元素。 elementData[--size] = null;因為arraycopy方法是將elementData的index+1處開始的元素往前複製,也就是說最後一個數本該消除,但還在那裡,所以需要置空。
- subList方法得到的subList將和原來的list互相影響,不管你改哪一個,另一個都會隨之改變,而且當父list結構改變時,子list會拋ConcurrentModificationException異常。解決方案:List<String> subListNew = new ArrayList(parentList.subList(1, 3));【類似Arrays.asList()方法】
- CAS演算法;unsafe.compareAndSwapInt(this, valueOffset, expect, update); CAS(Compare And Swap),意思是如果valueOffset位置包含的值與expect值相同,則更新valueOffset位置的值為update,並返回true,否則不更新,返回false。
- 與Java8的HashMap有相通之處,底層依然由“陣列”+連結串列+紅黑樹;
- 底層結構存放的是TreeBin物件,而不是TreeNode物件;
- CAS作為知名無鎖演算法,那ConcurrentHashMap就沒用鎖了麼?當然不是,hash值相同的連結串列的頭結點還是會synchronized上鎖。
private transient volatile int sizeCtl;
sizeCtl是控制識別符號,不同的值表示不同的意義。
- 負數代表正在進行初始化或擴容操作
- -1代表正在初始化
- -N 表示有N-1個執行緒正在進行擴容操作
- 正數或0代表hash表還沒有被初始化,這個數值表示初始化或下一次進行擴容的大小,類似於擴容閾值。它的值始終是當前ConcurrentHashMap容量的0.75倍,這與loadfactor是對應的。實際容量>=sizeCtl,則擴容。
並不是我們傳統的包含key-value的節點,只是一個標誌節點,並且指向nextTable,提供find方法而已。生命週期:僅存活於擴容操作且bin不為null時,一定會出現在每個bin的首位。
3個原子操作(呼叫頻率很高)
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演算法設定i位置上的Node節點(將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);
}
// 設定節點位置的值,僅在上鎖區被呼叫
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擴容操作:單執行緒構建兩倍容量的nextTable;允許多執行緒複製原table元素到nextTable。- 為每個核心均分任務,並保證其不小於16;
- 若nextTab為null,則初始化其為原table的2倍;
- 死迴圈遍歷,直到finishing。
- 節點為空,則插入ForwardingNode;
- 連結串列節點(fh>=0),分別插入nextTable的i和i+n的位置;【逆序連結串列??】
- TreeBin節點(fh<0),判斷是否需要untreefi,分別插入nextTable的i和i+n的位置;【逆序樹??】
- finishing時,nextTab賦給table,更新sizeCtl為新容量的0.75倍 ,完成擴容。
- 若table為空,則初始化,僅設定相關引數;
- @@@計算當前key存放位置,即table的下標i=(n - 1) & hash;
- 若待存放位置為null,casTabAt無鎖插入;
- 若是forwarding nodes(檢測到正在擴容),則helpTransfer(幫助其擴容);
- else(待插入位置非空且不是forward節點,即碰撞了),將頭節點上鎖(保證了執行緒安全):區分連結串列節點和樹節點,分別插入(遇到hash值與key值都與新節點一致的情況,只需要更新value值即可。否則依次向後遍歷,直到連結串列尾插入這個結點);
- 若連結串列長度>8,則treeifyBin轉樹(Note:若length<64,直接tryPresize,兩倍table.length;不轉樹)。
以下為引用:
java提高篇(二十)-----集合大家族:http://demo.netfoucs.com/chenssy/article/details/17732841
6.1、Vector和ArrayList
1,vector是執行緒同步的,所以它也是執行緒安全的,而arraylist是執行緒非同步的,是不安全的。如果不考慮到執行緒的安全因素,一般用arraylist效率比較高。
2,如果集合中的元素的數目大於目前集合陣列的長度時,vector增長率為目前陣列長度的100%,而arraylist增長率為目前陣列長度的50%.如過在集合中使用資料量比較大的資料,用vector有一定的優勢。
3,如果查詢一個指定位置的資料,vector和arraylist使用的時間是相同的,都是0(1),這個時候使用vector和arraylist都可以。而如果移動一個指定位置的資料花費的時間為0(n-i)n為總長度,這個時候就應該考慮到使用linklist,因為它移動一個指定位置的資料所花費的時間為0(1),而查詢一個指定位置的資料時花費的時間為0(i)。
ArrayList 和Vector是採用陣列方式儲存資料,此陣列元素數大於實際儲存的資料以便增加和插入元素,都允許直接序號索引元素,但是插入資料要設計到陣列元素移動等記憶體操作,所以索引資料快插入資料慢,Vector由於使用了synchronized方法(執行緒安全)所以效能上比ArrayList要差,LinkedList使用雙向連結串列實現儲存,按序號索引資料需要進行向前或向後遍歷,但是插入資料時只需要記錄本項的前後項即可