1.數組的缺點:大小一旦給定就沒法更改,除非複製到一個新的數組中,開銷大;而容器類均可以自動地調整本身的尺寸。
2.容器功能的多樣性:容器能夠實現各類不一樣要求,如按不一樣依據將元素進行排序或者保證容器內無重複元素等等。
關於容器的泛型參數:
當指定了某個類型做爲類型參數時,則能夠將該類型及其子類型的對象放入到容器當中,泛型保證了安全性。html
Collection保存單一的元素,而且Collection繼承了Iterable接口,而Map中保存鍵值對。java
Collection中提供了判空、大小、遍歷、是否包含某元素、是否包含其餘Collection所有、添加某元素、添加其餘Collecton所有、刪除某元素、刪除其餘Collection所有(求差)、刪除所有元素*以及、只保留與其餘Collection重合部分(求交)、以及toArray方法(全部Collection實現類都能轉換成數組而且如迭代器有是順序那麼數組中也是相同順序的)。算法
Collection一個重要的做用就是做爲它的具體實現集合之間相互轉換的中介,比較經常使用的Collection類如ArrayList、LinkedList、HashSet、LinkedHashSet、TreeSet中除了都有無參構造函數外還所有都有一個接受Collection做爲參數的構造函數(LinkedList有且僅有這兩個)。編程
其中ArrayList(10)、HashSet(16,0.75)、LinkedHashSet(16,0.75)都有一個在建立時指定容量的構造函數,對於ArrayList而言是由於其底層是基於數組實現的。api
其中HashSet和LinkedHashSet(LinkedHashSet繼承自HashSet)還多了一個能夠同時指定容量和負載因子的構造函數,如不指定則默認是0.75。這是由於HashSet內部是以一個HashMap對象實現的(構造函數中建立賦給Map類型的成員變量map)、LinkedHashSet中是以一個LinkedHashMap對象實現的(構造函數中調用父類的一個默認訪問權限級別的構造函數來建立而後一樣賦給map),由於HashMap和LinkedHashMap都是用數組+(雙向節點)鏈表來實現的,因此就有了容量和負載因子這兩個參數,也相應地有了這兩個構造函數。數組
其中TreeSet則有一個接受SortedSet做爲參數的構造函數和一個接受比較器Comparator做爲參數的構造函數。前者除了轉換集合類型外還有個做用是能夠按照本來SortedSet裏的比較器來進行排序(若是存在),也就是說轉換後新舊SortedSet裏面的元素順序是相同的。安全
//待補充...話說思否不能設置字體顏色的麼
Collection中的List有三個特色:1.能夠容許重複的對象。2.能夠插入多個null元素。3.是一個有序容器,保持了每一個元素的插入順序,輸出的順序就是插入的順序。List從新規定了equals和hashCode方法的實現,這使得equals能夠用來不一樣類型之間的List實現類對象之間來比較所包含元素是否徹底相同,這個相同是按順序相同的,即54321與12345是不相同的。
經常使用的實現類有ArrayList和LinkedList,當須要大量的隨機訪問則使用ArrayList,當須要常常從表前半部分插入和刪除元素則應該根據靠前程度使用LinkedList(由於對於ArrayList而言插入或者刪除元素的位置越靠前,須要複製元素的次數就越接近size(添加是size-i刪除是size-i-1);對於LinkedList而言,它是根據位置位於前半部分仍是後半部分來選則是從前日後遍歷找仍是從後往前找,對它而言位於插入或者刪除中間的元素反而是效率最低的。因此前部分是LinkedList比ArrayList效率更高的部分)。
除了繼承自Collection的方法,List接口還額外增長了如下方法:併發
新增了一個能夠返回更適合List的迭代器對象的方法listIterator(Iterator的子類)。ListIterator容許從任一方向來遍歷List對象,並在遍歷(迭代)過程當中進行修改該List對象,還能得到迭代器的當前位置。hasNext、next是正向遍歷,hasPrevious、previous是逆向遍歷。listIterator有兩個版本,一個是無參數的,會返回一個遊標指向List開頭的ListIterator,另外一個是帶有一個int參數的,會返回一個遊標指向指定位置的ListIterator。
混合調用next和previous會返回同一個對象,extIndex返回的是下次調用next所要返回的元素的位置,previousIndex返回的是下次調用previous所要返回的元素的位置。在同一遊標位置nextIndex老是比previousIndex更大的,如下是兩種邊界狀況:一種返回-1另外一種返回list.size() (1) a call to previousIndex when the cursor is before the initial element returns -1 and (2) a call to nextIndex when the cursor is after the final element returns list.size().oracle
subList,返回的List(banked)是由本來的List(banking)所支持的,所以對本來數組元素的更改會反映到返回的數組當中。任何能對List使用的方法對返回的subList同樣可以使用該方法可獲得一個banked List,但全部的方法都仍是應該推薦在baking List上使用,而不是banked List。
由於一旦你經過banking List或者另外一個由subList獲得的banked List2對鏈表進行告終構修改(其實就是增刪元素,修改元素的內容並無影響),那麼該baked List的全部public方法(除了subList外和一些繼承自Object的方法外),都會在運行時報ConcurrentModificationException異常。
Arrays.asList方法:可讓一個數組被看作是一個List,但該List底層的實現仍是本來的數組,對List中元素的更改也就是對數組的更改。數組是沒法調整大小的,也所以,這個List沒法add或者remove元素。app
ArrayList:內部使用了一個名爲elementData的Object數組引用變量(後面以數組變量稱呼),在ArrayList對象建立以後而且尚未添加元素以前,該變量默認指向一個static(因此變量位於方法區) final的引用變量DEFAULTCAPACITY_EMPTY_ELEMENTDATA,這個引用變量指向一個空的數組對象,這是爲了不咱們反覆去建立未使用的數組,若是當咱們反覆建立無用的數組了,那麼它們其中的elementData就全都順着引用指向着那一個空的數組對象了。
當咱們首次添加一個元素時,就會新建大小爲默認值10的Object數組對象傳給數組變量,而後將元素放進去。但這個時候size只會是1而不是10,由於size指代的是真正存放的元素的數量而不是容量。而當每次size剛超過容量時就會進行1.5倍的擴容,好比咱們有了10個元素裝滿了,如今添加第11個元素的時候就會把數組擴容成15,而後再將原數組複製過去以及第11個元素放進去,size變成11。實際上複製操做最底層都是經過System.arraycopy()來實現的,也就是直接賦值的淺拷貝(可見筆記關於Object的clone方法、淺拷貝、深拷貝),由於native方法效率比循環複製要高。
當在對ArrayList根據索引進行一次插入(複製次數size-i)或者刪除(複製次數size-i-1)元素的時候,索引越靠後,須要複製的次數越少,效率越高,索引越靠前須要複製的次數越多,效率越低。
LinkedList就是一個雙向鏈表的實現,它同時實現了List接口和Deque接口,也是說它便可以看做一個順序鏈表,又能夠看作一個隊列(Queue),同時又能夠看作一個棧(Satck)。
順便談下關於棧,在java中有個現成的棧類,就是java.util.Stack這個類,但這個類java官方在api中也指出再也不推薦使用。而是在須要使用這種先進後出的棧時推薦使用Deque接口下的實現類,好比這裏的LinkedList,但更推薦的是ArrayDeque。
LinkedList對指定位置的增刪查改,都會經過與size>>1(表示size/2,使用移位運算提高代碼的運行效率)的相比較的方式來選擇從前遍歷仍是從後遍歷。因此當要增刪查改的位置恰好位於中間時,效率是最低的。
Set的特色是不接受重複元素,其中TreeSet不接受null,由於TreeSet是用TreeMap實現的,TreeMap其實就是個紅黑樹,而在紅黑樹當中是不能插入一個空節點的;其餘兩個HashSet和LinkedHashSet則能夠接受null元素。Set從新規定了equals和hashCode方法的實現,這使得equals能夠用來不一樣類型之間的Set實現類對象之間來比較所包含元素是否徹底相同(與List相比不用順序相同)。
HashSet提供最快的查詢速度,而TreeSet保持元素處於排序狀態,LinkedHashSet以插入順序保存元素。其中LinkedHashSet是HashSet的子類。
HashSet底層是用一個HashMap來實現的,這個HashMap的全部鍵值映射的值都是同一個對象(一個Obect對象),就是下面代碼當中的final修飾的PRESENT。
private transient HashMap<E,Object> map; private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<>(); } //這裏的0.75還有16都是與HashMap中默認加載因子和默認容量是一致的 public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); }
HashSet中的大小、增、刪、查、遍歷等操做都是經過map來進行的,代碼以下所示,值的提一下的是remove方法,由於在HashSet裏面的全部元素做爲鍵對應值都是PRESENT,在map的remove方法當中會返回刪除元素的值(在這裏必定就是PRESENT了)或者null,因此用返回的值與PRESENT進行比較也是能夠得出是否刪除成功也是能夠的。
public Iterator<E> iterator() { return map.keySet().iterator(); } public int size() { return map.size(); } public boolean isEmpty() { return map.isEmpty(); } public boolean contains(Object o) { return map.containsKey(o); } public boolean add(E e) { return map.put(e, PRESENT)==null; } public boolean remove(Object o) { return map.remove(o)==PRESENT; } public void clear() { map.clear(); }
最後說下HashSet的專門爲LinkedHashSet預留的一個構造函數,這是一個包訪問權限的構造函數,實際上被設置爲只被LinkedHashSet所調用到了,由於LinkedHashSet繼承了HashSet。這個構造函數是將返回了一個LinkedHashMap對象給map,這也是LinkedHashMap的存儲實現原理。
/** * Constructs a new, empty linked hash set. (This package private * constructor is only used by LinkedHashSet.) The backing * HashMap instance is a LinkedHashMap with the specified initial * capacity and the specified load factor. * * @param initialCapacity the initial capacity of the hash map * @param loadFactor the load factor of the hash map * @param dummy ignored (distinguishes this * constructor from other int, float constructor.) * @throws IllegalArgumentException if the initial capacity is less * than zero, or if the load factor is nonpositive */ HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }
正如前面所述,LinkedHashSet是繼承了HashSet的,大部分操做也是直接繼承的,只有少部分本身的方法,而且構造器方法都是想上調用了HashSet的那個建立一個LinkedHashMap的構造器方法,以下所示。因此LinkedHashSet底層是用LinkedHashMap來實現的,
public LinkedHashSet(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor, true); } public LinkedHashSet(int initialCapacity) { super(initialCapacity, .75f, true); } public LinkedHashSet() { super(16, .75f, true); } public LinkedHashSet(Collection<? extends E> c) { super(Math.max(2*c.size(), 11), .75f, true); addAll(c); } @Override public Spliterator<E> spliterator() { return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.ORDERED); }
TreeSet底層是用TreeMap來實現的,以下面代碼所示在構造器函數中建立了一個TreeMap對象,並將其賦值給了m(NavigableMap接口類型,TreeMap也實現了該類),與HashSet一樣的作法:將鍵做爲元素,值則都指向PRESENT,一樣對應的查找刪除添加操做也都是調用TreeMap來完成的,這裏再也不重複說明。
private transient NavigableMap<E,Object> m; private static final Object PRESENT = new Object(); TreeSet(NavigableMap<E,Object> m) { this.m = m; } public TreeSet() { this(new TreeMap<E,Object>()); } public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); }
Queue經常使用的實現類是ArrayDeque和LinkedList,ArrayDeque是Deque 接口的大小可變數組的實現。數組雙端隊列沒有容量限制;它們可根據須要增長以支持使用。它們不是線程安全的;在沒有外部同步時,它們不支持多個線程的併發訪問。禁止 null 元素。此類極可能在用做堆棧時快於Stack,在用做隊列時快於LinkedList。
Queue中的三類方法以下:
peekFirst and peekLast return NULL.
(1) HashMap:它根據鍵的hashCode值存儲數據,大多數狀況下能夠直接定位到它的值,於是具備很快的訪問速度,但遍歷順序倒是不肯定的。 HashMap最多隻容許一條記錄的鍵爲null,容許多條記錄的值爲null。HashMap非線程安全,即任一時刻能夠有多個線程同時寫HashMap,可能會致使數據的不一致。若是須要知足線程安全,能夠用 Collections的synchronizedMap方法使HashMap具備線程安全的能力,或者使用ConcurrentHashMap。
(2) Hashtable:Hashtable是遺留類,不少映射的經常使用功能與HashMap相似,不一樣的是它承自Dictionary類,而且是線程安全的,任一時間只有一個線程能寫Hashtable,併發性不如ConcurrentHashMap,由於ConcurrentHashMap引入了分段鎖。Hashtable不建議在新代碼中使用,不須要線程安全的場合能夠用HashMap替換,須要線程安全的場合能夠用ConcurrentHashMap替換。
(3) LinkedHashMap:LinkedHashMap是HashMap的一個子類,保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先獲得的記錄確定是先插入的,也能夠在構造時帶參數,按照訪問次序排序。
(4) TreeMap:TreeMap實現SortedMap接口,可以把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也能夠指定排序的比較器,當用Iterator遍歷TreeMap時,獲得的記錄是排過序的。若是使用排序的映射,建議使用TreeMap。在使用TreeMap時,key必須實現Comparable接口或者在構造TreeMap傳入自定義的Comparator,不然會在運行時拋出java.lang.ClassCastException類型的異常。
對於上述四種Map類型的類,要求映射中的key是不可變對象。不可變對象是該對象在建立後它的哈希值不會被改變。若是對象的哈希值發生變化,Map對象極可能就定位不到映射的位置了。
關於HashMap主要看這篇就行了Java 8系列之從新認識HashMap,而後下面是從文中稍微摘取了自認爲幾個比較重要的點吧:
這也符合散列表以空間換時間的特色,小於1的負載因子的存在就是爲了讓數組中的鏈表長度儘量短,由於鏈表查找是更花費時間相比於數組的訪問,負載因子如爲1就是在鍵的hash值擾動取模後均勻分佈的理想狀況下,每一個桶內的元素個數指望是1,鏈表長度就只是1,這是最理想的狀況了。但實際上分佈達不到這麼均勻,爲了減小鏈表長度把負載因子設置成了0.75來保證鏈表長度儘量短。固然僅這一種減小時間的作法java官方可能還不夠,若是就是出現了小几率事件某個桶內鏈表長度比較長怎麼辦,java8引入了樹化桶方法。在某個桶內鏈表長度大於8時將其該鏈表轉換爲紅黑樹結構
但僅僅直接是原始的hashCode值低位信息顯然是不行的,hashCode值是爲了保證總體均勻的(即儘量不一樣的對象對應不一樣的散列碼),低位可能並不怎麼均勻,爲了解決這種狀況HashMap中會將原始的hashCode值與高16位進行一個異或(不一樣爲1相同爲0)操做,這樣就混合了原始hashCode低位和高位,加大低位隨機均勻性。而後用這個混合後的hash值再去進行按位與運算。
以上也就是爲何要使用擾動函數、默認容量爲16以及本身設定的容量會被自動提高爲最近的2次冪大小的緣由。
/** * 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(若是值不爲null,不進行覆蓋,這是爲putIfAbsent這種插入方法準備的) * @param evict if false, the table is in creation mode. * evict參數是由於LinkedHashMap預留了一個能夠鏈表中長度固定,並保持最新的N的節點數據的方法afterNodeInsertion(由於removeEldestEntry始終返回false因此目前並不生效), * 能夠經過重寫removeEldestEntry來就能進行實現了。 * @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; // 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; //則先看hash值是否相同(來到這個位置是根據的是hash擾動後的值,可能hash值就不同),鍵的內存地址是否相同,鍵的內容是否相等。 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) // 保證了在插入後鏈表長度爲9時就進入桶樹化函數 treeify是樹化的意思。 //但這個函數只會在數組長度大於等於64時進行將hash肯定的這個桶內的鏈表轉換成紅黑樹,對應結點也轉換成了紅黑樹結點。 //若是數組長度小於64時就只會進行擴容操做了,而不是轉換成紅黑樹,由於擴容後極可能鏈表長度就減小了 treeifyBin(tab, hash); break;//樹化桶執行完畢以後結束循環,而且這個時候的e是null } //在循環中查找有無鍵相等的結點 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) //看hash值是否相同(來到這個位置是根據的是hash擾動後的值,可能hash值就不同),鍵的內存地址是否相同,鍵的內容是否相等。 break;//相等則從循環中跳出來,這個時候的e保存的是桶內鍵相等的結點的引用 p = e; } } //e!=null說明e桶內鍵相等的結點的引用,則進行值的覆蓋 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
LinkedHashMap是HashMap的子類,相比HashMap增長的就是更換告終點爲本身的內部靜態類LinkedHashMap.Entry,這個Entry繼承自HashMap.Node,增長了before和after指針用來記錄插入順序。以下圖[via9]所示
用紅黑樹實現,關於紅黑樹實現原理已經寫過了就再也不寫了。
這兩個List實現類都不是同步的。若是多個線程同時訪問一個ArrayList或者LinkedList實例,而其中至少一個線程從結構上修改了列表,那麼它必須保持外部同步。(結構上的修改是指任何添加或刪除一個或多個元素的操做,或者顯式調整底層數組的大小;僅僅設置元素的值不是結構上的修改。)
這通常經過對封裝該List的對象進行同步操做來完成。若是不存在這樣的對象,則應該使用Collections.synchronizedList方法將該列表「包裝」起來。這最好在建立時完成,以防止意外對列表進行不一樣步的訪問,以下所示:
List list = Collections.synchronizedList(new ArrayList(...)); List list = Collections.synchronizedList(new LinkedList(...));
注意,這三個Set實現類都不是同步的。若是多個線程同時訪問HashSet、LinkedHashSet或者TreeSet,而其中至少一個線程修改了該set,則它必須保持外部同步。
這通常經過對封裝該set的對象進行同步操做來完成。若是不存在這樣的對象,則應該使用 Collections.synchronizedSet (對於TreeSet是Collections.synchronizedSortedSet)方法來「包裝」該 set。最好在建立時完成這一操做,以防止意外的非同步訪問:
Set s = Collections.synchronizedSet(new LinkedHashSet(...)); Set s = Collections.synchronizedSet(new HashSet(...)); SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));
注意,這三個Map實現類都不是同步的。若是多個線程同時訪問一個HashMap、LinkedHashMap或者TreeMap,而其中至少一個線程從結構上修改了該映射,則它必須 保持外部同步。這通常經過對天然封裝該映射的對象進行同步操做來完成。若是不存在這樣的對象,則應該使用 Collections.synchronizedMap(對於TreeMap應該使用Collections.synchronizedSortedMap)方法來「包裝」該映射。最好在建立時完成這一操做,以防止對映射進行意外的非同步訪問,以下所示:
Map m = Collections.synchronizedMap(new HashMap(...)); Map m = Collections.synchronizedMap(new LinkedHashMap(...)); SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
(結構上的修改是指添加或刪除一個或多個映射關係的任何操做;僅改變與實例已經包含的鍵關聯的值不是結構上的修改。對於LinkedHashMap而言,當按訪問排序的HashMap時,結構上的修改還包括影響迭代順序的任何操做,但此時僅利用get查詢LinkedHashMapMap不是結構修改)
參考文章:
Java™ Platform Standard Ed. 8 Api
在中間位置添加元素,ArrayList比LinkedList效率更高?
arraylist add(int index) 方法時 index是處於前半部分仍是後半部分效率高
ArrayList初始化
ArrayList底層數組擴容原理
List、Set、Map的區別
Java集合框架源碼剖析:LinkedHashSet 和 LinkedHashMap
淺談java 集合框架
JDK 源碼中 HashMap 的 hash 方法原理是什麼?胖君的回答
編程語言中,取餘和取模的區別究竟是什麼?
深刻理解 hashcode 和 hash 算法