集合在Java中的地位想必你們都知道,不用多BB了。不管是在咱們如今的學習中仍是在從此的工做中,集合這樣一個你們族都無處不在,無處不用。在前面講到的數組也是一個小的容器,可是數組不是面向對象對象的,它存在明顯的缺陷,而集合剛好彌補了數組帶來的缺陷。集合比數組更加靈活、更加實用。並且不一樣的集合框架可用於不一樣的場景。html
咱們簡單來比較一下數組和集合區別:java
爲了清晰的認識Java集合你們族,下面是整個Java集合的框架圖:算法
經過上面的圖片能夠看到集合你們族的成員以及他們之間的關係。發現這也太多了吧,不過不要太慌張。咱們如今只須要抓住它們的主幹便可,即Collection、Map和Iterator,額外的還有兩個對集合操做的工具類Collections和Arrays。其中虛框表示的是接口或抽象類,實框是類,虛箭頭是實現,實箭頭是繼承。而後把它們捋一捋給分個類就應該清楚了。數組
上面的Java集合框架圖看起來比較的雜亂,因此咱們對它們進行了分類處理,這樣更加直觀。安全
Collection接口:最基本的集合接口(單列數據)
├——-List接口:全部元素按照進入的前後順序有序存儲,可重複集合
│—————-├ ArrayList:接口實現類,用數組實現,隨機訪問,增刪慢,查詢快,沒有同步,線程不安全
│—————-├ LinkedList:接口實現類,用雙向鏈表實現, 插入刪除快,查詢慢, 沒有同步,線程不安全
│—————-└ Vector:接口實現類,用數組實現,它和ArrayList幾乎同樣,可是它是同步, 線程安全的(Vector幾乎已經不用了)
└———————-└ Stack:繼承自Vector類,Stack具備後進先出的特色
└——-Set接口:不容許包含重複的值(能夠有null,可是隻有一個),無序,不可重複集合
├—————-└HashSet:使用hash表(數組)存儲元素,無序,其底層是包裝了一個HashMap去實現的,因此查詢插入速度較快
│————————└ LinkedHashSet:繼承HashSet類,它新增了一個重要特性,就是元素按照插入的順序存儲
├ —————-TreeSet:底層基於TreeMap實現的,它支持2種排序方式:天然排序(Comparable)和定製排序(Comparator)數據結構
└ ——-Queue接口:隊列,它的特色是先進先出框架
Map接口:鍵值對的集合接口,不容許含有相同的key,有則輸出一個key-value組合(雙列數據)
├———Hashtable:接口實現類,用hash表實現,不可重複key,key不能爲null,同步,效率稍低,線程安全
├———HashMap:接口實現類 ,用hash表實現,不可重複key,key能夠爲null,沒有同步,效率稍高 ,線程不安全
│—————–├ LinkedHashMap:雙向鏈表和hash表實現,按照key的插入順序存放
│——— WeakHashMap:和HashMap同樣,但它的鍵是「弱鍵」,垃圾收集器會自動的清除沒有在其餘任何地方被引用的鍵值對ide
├ ——–TreeMap:用紅黑樹算法實現,它默認按照全部的key進行排序
└———IdentifyHashMap:它是一個特殊的Map實現,它的內部判斷key是否相等用的是 ==,而HashMap則更加複雜函數
簡單完成分類以後咱們就從集合的特色和區別來進行一 一講解。工具
Collection是最基本的集合接口,它是單列數據集合。在JDK中不提供Collection接口的任何直接實現,它只提供了更具體的子接口(即繼承自Collection接口),例如List列表,Set集合,Queue隊列,而後再由具體的類來實現這些子接口。經過具體類實現接口以後它們的特徵就得以凸顯出來,有些集合中的元素是有序的,而其餘的集合中的元素則是無序的;有些集合容許重複的元素,而其餘的集合則不容許重複的元素;有些集合容許排序,而其餘的集合則不容許排序。
既然Collection接口是集合的層次結構的根接口,那麼一定有經常使用的方法,咱們來看一下:
咱們從上面Collection結構中能夠看到其內部有一個iterator()方法,這個方法不是Collection中所特有的,而是重寫了父類Iterable中的。由於Collection接口繼承了java.lang.Iterable接口,而該接口中有一個iterator()方法。也就是說全部實現了Collection接口的集合類中都有iterator()方法,它用來返回實現了Iterator接口的迭代器對象。
Iterator接口的內部結構比較簡單,其內部只定義的四個方法,它們分別是:
迭代器的簡單舉例:
public static void main(String[] args) { //hasNext()、next()測試 Collection coll = new ArrayList(); coll.add(123); coll.add(456); coll.add('A'); coll.add(true); coll.add(new String("Collection...")); //建立迭代器對象 Iterator iterator = coll.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } System.out.println("----------------------"); //remove()測試。若是還未調用next()或在上次調用next()方法以後已經調用了remove()方法, //那麼在再次用remove()方法會報錯java.lang.IllegalStateException Iterator iterator1 = coll.iterator(); while (iterator1.hasNext()) { //iterator1.remove();不能在next()前先調用 Object next = iterator1.next(); if (next.equals(456)) { iterator1.remove(); //iterator1.remove();不能在next()後再次調 } } iterator1 = coll.iterator(); while (iterator1.hasNext()) { System.out.println(iterator1.next()); } } //運行結果: //123 //456 //A //true //Collection... //---------------------- //123 //A //true //Collection...
簡易分析一下:當返回了Iterator對象以後能夠理解爲有一個指針,它指在第一個對象的上面(即123的上面,此時指針爲空),當咱們調用iterator.hasNext()的時候,判斷是否還有下一個元素,若是有則返回true。而後調用iterator.next()使指針往下移而且返回下移之後集合位置上的元素,這樣以此類推就輸出了以上結果。
List接口直接繼承了Collection接口,它對Collection進行了簡單的擴充,從而讓集合凸顯出它們各自的特徵。在List中全部元素的存儲都是有序的,並且是可重複存儲的。用戶能夠根據元素存儲位置的索引來操做元素。實現了List接口的集合主要有如下幾個:ArrayList、LinkedList、Vector和Stack。
注意:List集合它有一個特有的迭代器——ListIterator。Iterator的子接口ListIterator是專門給List集合提供的迭代元素的接口,它的內部對Iterator功能進行一些擴充。例如增長的方法有hasPrevious()、nextIndex()、previousIndex()等等。
既然List接口直接繼承了Collection接口,並且List是有序存儲結構,那麼List除了從Collection中繼承的方法以外,一定會本身添加一些根據索引來操做集合元素的方法,咱們來看一下:
ArrayList應該是咱們最多見的集合,同時也是List中最重要的一個。ArrayList的特色是:隨機訪問、查詢快,增刪慢,輕量級,線程不安全。它的底層是用Object數組實現的,咱們能夠把ArrayList看作是一個可改變大小的數組。隨着愈來愈多的元素被添加到ArrayList中,其容量是動態增長的(初始化容量是10,增量是原來的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1);)。
LinkedList底層是經過雙向鏈表實現的,因此它不能隨機訪問,並且須要查找元素必需要從開頭或結尾(從靠近指定索引的一端)開始一個一個的找。使用雙向鏈表則讓增長、刪除元素比較的方便,但查詢變得困難。因此LinkedList的特色是:查詢慢,增刪快,線程不安全。
因爲LinkedList是雙向鏈表實現的,因此它除了有List中的基本操做方法外還額外提供了一些方法在LinkedList的首部或尾部進行操做的方法(實際上是繼承Deque中的),如addXXX()、getXXX()、removeXXX()等等。同時,LinkedList還實現了Queue接口的子接口Deque,因此他還提供了offer(), peek(), poll()、pop()、push()等方法。咱們簡單來看一下:
Vector和ArrayList幾乎同樣,它們都是經過Object數組來實現的。可是Vector是線程安全的,和ArrayList相比,Vector中不少方法是用synchronized關鍵字處理過來保證證數據安全,這就必然會影響其效率,因此你的程序若是不涉及到線程安全問題,那麼推薦使用ArrayList集合。其實不管如何你們都會選擇ArrayList的,由於Vector已經不多用了,幾乎面臨淘汰。
另外兩者還有一個區別就是Vector和ArrayList的擴容策略不同,Vector的擴容增量是原來容量的2倍,而ArrayList是原來的1.5倍。
Stack的名稱是堆棧。它是繼承自Vector這個類,這就意味着,Stack也是經過數組來實現的。Stack的特性是:先進後出(FILO, First In Last Out)。此外Stack中還提供5個額外的方法使得Vector得以被看成堆棧使用。咱們來看一下這五個方法:
Set和List同樣都是繼承自Collection接口,可是Set和List的特色徹底不同。Set集合中的元素是無順序的,且沒有重複的元素。若是你試圖將多個相同的對象添加到Set中,那麼很差意思,它會立馬阻止。Set中會用equals()和hashCode()方法來判斷兩個對象是否相同,只要該方法的結果是true,Set就不會再次接收這個對象了。實現了Set接口主要有一下幾個:HashSet、LinkedHashSet、TreeSet、EnumSet。Set中沒有增長任何新的方法,用的都是繼承自中Collection中的。
HashSet是按照哈希算法(hashCode)來存儲集合中的對象,因此它是無序的,同時也不能保證元素的排列順序。其底層是包裝了一個HashMap去實現的,因此其查詢效率很是高。並且在增長和刪除的時候因爲運用hashCode的值來比較肯定添加和刪除元素的位置,因此不存在元素的偏移,效率也很是高。所以HashSet的查詢、增長和刪除元素的效率都是很是高的。可是HashSet增刪的高效率是經過花費大量的空間換來的:由於空間越大,取餘數相同的狀況就越小。HashSet這種算法會創建許多無用的空間。使用HashSet接口時要注意,若是發生衝突,就會出現遍歷整個數組的狀況,這樣就使得效率很是的低。HashSet使用簡單舉例:
public class HashSetTest { public static void main(String[] args) { //建立HashSet的實例 HashSet<String> hashSet = new HashSet<>(); //添加了兩個AA元素 hashSet.add("AA"); hashSet.add("AA"); hashSet.add("BB"); //添加了兩個CC元素 hashSet.add("CC"); hashSet.add("CC"); hashSet.add("DD"); hashSet.add("EE"); hashSet.add("FF"); hashSet.add("GG"); //遍歷打印結果 for (String s : hashSet) { System.out.println(s+",hash值是:"+s.hashCode()); } } } //運行結果: //AA,hash值是:2080 //BB,hash值是:2112 //CC,hash值是:2144 //DD,hash值是:2176 //EE,hash值是:2208 //FF,hash值是:2240 //GG,hash值是:2272
LinkedHashSet繼承自HashSet類,它不只實現了哈希算法(hashCode),還實現了鏈表的數據結構,提供了插入和刪除的功能。他有HashSet所有特性,但它新增了一個重要特性,就是元素按照插入的順序存儲。因此當遍歷LinkedHashSet集合裏的元素時,LinkedHashSet將會按元素的添加順序來訪問集合裏的元素。正是由於多加了這樣一種數據結構,因此它的效率較低,不建議使用,若是要求一個集合急要保證元素不重複,也須要記錄元素的前後添加順序,才選擇使用LinkedHashSet。LinkedHashSet使用簡單舉例:
public class LinkedHashSetTest { public static void main(String[] args) { //建立LinkedHashSet實例 LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>(); //無序添加元素 linkedHashSet.add("DD"); linkedHashSet.add("BB"); linkedHashSet.add("AA"); linkedHashSet.add("GG"); linkedHashSet.add("EE"); linkedHashSet.add("FF"); linkedHashSet.add("CC"); //遍歷打印值 for (String s : linkedHashSet) { System.out.println(s+",hash值:"+s.hashCode()); } //刪除GG元素 boolean gg = linkedHashSet.remove("GG"); System.out.println(gg); } } //運行結果: //DD,hash值:2176 //BB,hash值:2112 //AA,hash值:2080 //GG,hash值:2272 //EE,hash值:2208 //FF,hash值:2240 //CC,hash值:2144 //true
TreeSet的底層是基於TreeMap中實現的。它不只能保證元素的惟一性,還能對元素按照某種規則進行排序。它繼承了AbstractSet抽象類,實現了NavigableSet <E>,Cloneable,可序列化接口。而NavigableSet <E>又繼承了SortedSet接口,此接口主要用於排序操做,即實現此接口的子類都屬於排序的子類,有可排序的功能。TreeSet中的元素支持2種排序方式:天然排序或者定製排序,使用方式具體取決於咱們使用的構造方法(默認使用天然排序)。TreeSet使用簡單舉例:
public class TreeSetTest { public static void main(String[] args) { //建立TreeSet實例、使用Comparator定製排序 TreeSet<Integer> treeSet = new TreeSet<>(new Comparator<Integer>() { //排序方式爲:降序 @Override public int compare(Integer o1, Integer o2) { if (o1>o2){ return -1; }else if(o1<o2){ return 1; }else { return 0; } } }); treeSet.add(2); treeSet.add(2); treeSet.add(4); treeSet.add(4); treeSet.add(1); treeSet.add(1); treeSet.add(3); treeSet.add(3); for (Integer integer : treeSet) { System.out.println(integer); } } } //輸出結果: //4 //3 //2 //1
EnumSet是專門爲枚舉類而設計的有序集合類,EnumSet中全部元素都必須是指定枚舉類型的枚舉值,在建立EnumSet時必須顯式或隱式指定它對應的枚舉類。EnumSet使用簡單舉例:
//建立枚舉類 public enum Season { SPRING,SUMMER,AUTUMN,WINNER; } class Test{ public static void main(String[] args) { //EnumSet的簡單使用 EnumSet<Season> seasons = EnumSet.allOf(Season.class); //遍歷 for (Season season : seasons) { System.out.println(season); } } } //輸出結果: //SPRING //SUMMER //AUTUMN //WINNER
Queue接口與List、Set是同一級別的,都繼承了Collection接口。Queue表示的是隊列,它的特色是:先進先出(FIFO,First-in-First-Out) 。隊列主要分爲兩大類:一類是BlockingDeque阻塞隊列(Queue的子接口),它的主要實現類包括ArrayBlockQueue、PriorityBlockingQueue. LinkedBlockingQueue。另外一類是Deque雙端隊列(也是Queue的子接口),支持在頭部和尾部兩端插入和移除元素,主要實現類包括:ArrayDeque、LinkedList。
Queue接口中包含的方法有:
對於BlockingDeque阻塞隊列的詳解能夠參考這篇博客:BlockingQueue(阻塞隊列)詳解 ,寫的很是不錯。
Map接口與Collection接口是徹底不一樣的。Map中保存的是具備「映射關係」的數據,便是由一系列鍵值對組成的集合,提供了key到value的映射,也就是說一個key對應一個value,其中key和value均可以是任何引用數據類型。可是Map中不能存在相同的key值,對於相同key值的Map對象會經過equals()方法來判斷key值是否相等,只要該方法的結果是true,Map就不會再次接收這個對象了,固然value值能夠相同。實現了Map接口的類主要的有如下幾個:HashMap、Hashtable、LinkedHashMap、WeakHashMap、TreeMap、IdentifyHashMap、EnumMap。其中Map集合還和Set集合有着很是緊密的聯繫,由於不少Set集合中底層就是用Map來實現的。
咱們來看一下Map鍵值對根接口中的方法:
遍歷Map中的對象:Java中四種遍歷Map對象的方法
Hashtable是一個古老的Map實現類,在JDK1.0就提供了,它繼承自Dictionary類,底層基於hash表結構+數組+鏈表實現實現,內部全部方法都是同步的,即線程很安全,可是效率低。注意在Hashtable中key和value均不可爲null。
HashMap是Map集合中使用最多的,它是Hashtable的一個輕量級版本,它是繼承了Abstractmap類,底層也是基於hash表結構+鏈表(紅黑樹,鏈表長度大於8會將鏈表轉化成紅黑樹)+數組實現的,內部全部方法是不一樣步的,即線程不安全,可是效率高。在HashtMap中key和value均都可爲null。
LinkedHashMap基於雙向鏈表和數組實現,內部結構和HashMap相似,就是多加了一個雙向鏈表結構。根據key的插入順序進行存儲。(注意和TreeMap對全部的key-value進行排序進行區分)
WeakHashMap與HashMap的用法基本類似。區別在於,HashMap的key保留了對實際對象的"強引用",這意味着只要該HashMap對象不被銷燬,該HashMap所引用的對象就不會被垃圾回收。但WeakHashMap的key只保留了對實際對象的弱引用,這意味着若是WeakHashMap對象的key所引用的對象沒有被其餘強引用變量所引用,則這些key所引用的對象可能被垃圾回收,當垃圾回收了該key所對應的實際對象以後,WeakHashMap也可能自動刪除這些key所對應的key-value對。
TreeMap底層是用紅黑樹算法實現,它實現SortMap接口,因此其內部元素默認按照全部的key進行排序。固然也支持2種排序方式:天然排序或者定製排序。其中TreeMap中的key不可null,而它非線程安全的。使用能夠參考上面的TreeSet示例,使用方式差很少。
IdentityHashMap是一種可重複key的集合類。它和HashMap相似,因此咱們通常都是拿IdentityHashMap和HashMap來進行比較,由於它們二者判斷重複key的方式不同。IdentifyHashMap中判斷重複key相等的條件是:(k1==k2),也就是說它只比較普通值是否相等,不比較對象中的內容。而HashMap中類判斷重複key相等的條件是: (k1==null?k2==null:k1.equals(k2))==true),它不只比較普通值,並且比對象中的內容是否相等。簡單舉例:
public class MapTest { public static void main(String[] args) { //建立HashMap實例,添加Integer實例爲key HashMap hashMap=new HashMap(); hashMap.put(1,"hello"); hashMap.put(1,"hello"); hashMap.put(new Integer(2),"hello"); hashMap.put(new Integer(2),"hello"); hashMap.put(new Integer(2),"hello"); System.out.println("HashMap:"+hashMap.toString()); //建立IdentityHashMap實例,添加Integer實例爲key IdentityHashMap<Object, Object> identityHashMap = new IdentityHashMap<>(); identityHashMap.put(3,"world"); identityHashMap.put(3,"world"); identityHashMap.put(new Integer(4),"world"); identityHashMap.put(new Integer(5),"world"); identityHashMap.put(new Integer(4),"world"); System.out.println("IdentityHashMap:"+identityHashMap.toString()); } } //運行結果: //HashMap:{1=hello, 2=hello} //IdentityHashMap:{4=world, 4=world, 3=world, 5=world}
EnumMap這個類是專門爲枚舉類而設計的有鍵值對的集合類。集合中的全部鍵(key)都必須是單個同一個類型的枚舉值,建立EnumMap時必須顯式或隱式指定它對應的枚舉類。當EnumMap建立後,其內部是以數組形式保存,因此這種實現形式很是緊湊高效。EnumMap根據key的天然順序(即枚舉值在枚舉類中的定義順序)來維護來維護key-value對的次序。能夠經過keySet()、entrySet()、values()等方法來遍歷EnumMap便可看到這種順序。EnumMap不容許使用null做爲key值,但容許使用null做爲value。若是試圖使用null做爲key將拋出NullPointerException異常。若是僅僅只是查詢是否包含值爲null的key、或者僅僅只是使用刪除值爲null的key,都不會拋出異常。EnumMap的代碼示例以下:
//建立枚舉類 public enum Season { SPRING,SUMMER,AUTUMN,WINNER; } class Test{ public static void main(String[] args) { //EnumMap的簡單使用 EnumMap<Season,String> map=new EnumMap<Season, String>(Season.class); map.put(Season.SPRING,"春暖花開"); map.put(Season.SUMMER,"夏日炎炎"); map.put(Season.AUTUMN,"秋高氣爽"); map.put(Season.WINNER,"冬暖夏涼"); //遍歷map有不少種方式,這裏是map遍歷的一種方式,這種方式是最快的 for (EnumMap.Entry<Season,String> entry:map.entrySet()){ System.out.println(entry.getKey()+","+entry.getValue()); } } } //輸出結果: //SPRING,春暖花開 //SUMMER,夏日炎炎 //AUTUMN,秋高氣爽 //WINNER,冬暖夏涼
Collections是集合的一個工具類或者幫助類,其中提供了一系列靜態方法,用於對集合中元素進行排序、搜索以及線程安全等各類操做。
以上只是一些常見的方法,若是須要了解更多的方法能夠自行去查看Collections的API。
Relevant Link: