Java入門系列(七)Java 集合框架(JCF, Java Collections Framework)
Java 集合框架圖
List、Set、Map可以看做集合的三大類
List
List代表一種線性表的數據結構, List集合是有序集合,集合中的元素可以重復,訪問集合中的元素可以根據元素的索引來訪問。
ArrayList則是一種順序存儲的線性表。ArrayList 底層采用數組來保存每個集合元素。線程不安全。ArrayList源碼分析
遍歷List集合的三種方法
List<String> list = new ArrayList<String>(); list.add("aaa"); list.add("bbb"); list.add("ccc"); //方法一:foreach for(String attribute : list) { System.out.println(attribute); } //方法二:對於ArrayList來說速度比較快, 用for循環, 以size為條件遍歷: for(int i = 0 ; i < list.size() ; i++) { System.out.println(list.get(i)); } //方法三:集合類的通用遍歷方式, 從很早的版本就有, 用叠代器叠代 Iterator it = list.iterator(); while(it.hasNext()) { System.out.println(it.next()); }
LinkedList 則是一種鏈式存儲的線性表。其本質上就是一個雙向鏈表,但它不僅實現了 List 接口,還實現了 Deque 接口。
也就是說LinkedList既可以當成雙向鏈表使用,也可以當成隊列使用,還可以當成棧來使用(Deque 代表雙端隊列,既具有隊列的特征,也具有棧的特征)。線程不安全。
@Test public void test2(){ Queue<String> queue = new LinkedList<String>(); queue.offer("Hello"); queue.offer(Queue 隊列"World!"); queue.offer("你好!"); System.out.println("隊列長度:"+queue.size()); String str; while((str=queue.poll())!=null){ System.out.println(str); } System.out.println("隊列長度:"+queue.size()); }
Arraylist和Linklist區別:Java 的 List 集合本身就是線性表的實現,其中 ArrayList是線性表的順序存儲實現;而 LinkedList 則是線性表的鏈式存儲實現。
1、Arraylist(優點):它是實現了基於動態數組的數據結構,因為地址連續,一旦數據存儲好了,查詢操作效率會比較高(在內存裏是連著放的)。
(缺點):因為地址連續, ArrayList要移動數據,所以插入和刪除操作效率比較低。
2、Linklist(優點):LinkedList基於鏈表的數據結構,地址是任意的,所以在開辟內存空間的時候不需要等一個連續的地址,對於新增和刪除操作add和remove,LinedList比較占優勢。
LinkedList 適用於要頭尾操作或插入指定位置的場景
(缺點):因為LinkedList要移動指針,所以查詢操作性能比較低。
適用場景分析:當需要對數據進行多次訪問的情況下選用ArrayList,當需要對數據進行多次增加刪除修改時采用LinkedList。
Vector向量類具體類:底層數據結構是數組。線程安全。
Vector 其實就是 ArrayList 的線程安全版本, ArrayList 和 Vector 絕大部分方法的實現都是相同的,只是 Vector 的方法增加了 synchronized 修飾。
ArrayList 的序列化實現比 Vector 的序列化實現更安全,因此 Vector 基本上已經被ArrayList 所代替了。 Vector 唯一的好處是它是線程安全的。
Stack具體類
public class Stack<E> extends Vector<E>
@Test public void test2(){ Stack<String> stack = new Stack<String>(); System.out.println("now the stack is " + isEmpty(stack)); stack.push("1"); stack.push("2"); stack.push("3"); stack.push("4"); stack.push("5"); System.out.println("now the stack is " + isEmpty(stack)); System.out.println(stack.peek()); //peek 不改變棧的值(不刪除棧頂的值) System.out.println(stack.pop()); //pop會把棧頂的值刪除 System.out.println(stack.search("1")); //方法調用返回從堆棧中,對象位於頂部的基於1的位置。 } public static String isEmpty(Stack<String> stack) { return stack.empty() ? "empty" : "not empty"; }Stack
Deque雙端隊列
Java 也不再推薦使用 Stack 類,而是推薦使用 Deque 實現類。
在無需保證線程安全的情況下,程序完全可以使用ArrayDueue來代替Stack 類。
從 JDK 1.6 開始,Java 為 Deque提供了一個常用的實現類ArrayDeque。就像List集合擁有 ArrayList 實現類一樣,Deque集合則擁有ArrayDeque 實現類。
@Test public void test1() { ArrayDeque stack = new ArrayDeque(); // 依次將三個元素push入“棧”,先進後出 stack.push("瘋狂Java講義"); stack.push("輕量級Java EE企業應用實戰"); stack.push("瘋狂Android講義"); System.out.println(stack); // [瘋狂Android講義, 輕量級Java EE企業應用實戰, 瘋狂Java講義] System.out.println(stack.peek()); // 瘋狂Android講義 System.out.println(stack); // [瘋狂Android講義, 輕量級Java EE企業應用實戰, 瘋狂Java講義] System.out.println(stack.pop()); // 瘋狂Android講義 System.out.println(stack);// [輕量級Java EE企業應用實戰, 瘋狂Java講義] // 當做隊列來使用,先進先出 ArrayDeque queue = new ArrayDeque(); queue.offer("瘋狂Java講義"); queue.offer("輕量級JavaEE企業應用實踐"); queue.offer("瘋狂Android講義"); System.out.println(queue); // [瘋狂Java講義, 輕量級JavaEE企業應用實踐, 瘋狂Android講義] // 訪問隊列頭部元素,但不將其poll出隊列 System.out.println(queue.peek()); System.out.println(queue); // poll出第一個元素 System.out.println(queue.poll()); System.out.println(queue);// [輕量級JavaEE企業應用實踐, 瘋狂Android講義] }ArrayDeque--stack || queue
Deque 接口代表雙端隊列這種數據結構。 雙端隊列已經不再是簡單的隊列了,它既具有隊列的性質先進先出( FIFO),也具有棧的性質( FILO),也就是說雙端隊列既是隊列,也是棧。
@Test public void test1(){ Deque<String> deque = new LinkedList<String>(); deque.add("d"); deque.add("e"); deque.add("f"); //從隊首取出元素,不會刪除 System.out.println("隊首取出元素:"+deque.peek()); System.out.println("隊列為:"+deque); //從隊首加入元素(隊列有容量限制時用,無則用addFirst) deque.offerFirst("c"); System.out.println("隊首加入元素後為:"+deque); //從隊尾加入元素(隊列有容量限制時用,無則用addLast) deque.offerLast("g"); System.out.println("隊尾加入元素後為:"+deque); //隊尾加入元素 deque.offer("h"); System.out.println("隊尾加入元素後為:"+deque); //獲取並移除隊列第一個元素,pollFirst()也是,區別在於隊列為空時,removeFirst會拋出NoSuchElementException異常,後者返回null deque.removeFirst(); System.out.println("獲取並移除隊列第一個元素後為:"+deque); //獲取並移除隊列第一個元素,此方法與pollLast 唯一區別在於隊列為空時,removeLast會拋出NoSuchElementException異常,後者返回null deque.removeLast(); System.out.println("獲取並移除隊列最後一個元素後為:"+deque); //獲取隊列第一個元素.此方法與 peekFirst 唯一的不同在於:如果此雙端隊列為空,它將拋出NoSuchElementException,後者返回null System.out.println("獲取隊列第一個元素為:"+deque.getFirst()); System.out.println("獲取隊列第一個元素後為:"+deque); //獲取隊列最後一個元素.此方法與 peekLast 唯一的不同在於:如果此雙端隊列為空,它將拋出NoSuchElementException,後者返回null System.out.println("獲取隊列最後一個元素為:"+deque.getLast()); System.out.println("獲取隊列第一個元素後為:"+deque); //循環獲取元素並在隊列移除元素 while(deque.size()>0){ System.out.println("獲取元素為:"+ deque.pop()+" 並刪除"); } System.out.println("隊列為:"+deque); }Deque--LinkedList
繼承關系是:deque => queue => collection=》Iterable
1.使用隊列的時候,new LinkedList的時候為什麽用deque接收,不用LinkedList呢?
答:deque繼承queue接口,因為它有兩個實現,LinkedList與ArrayDeque。用deque接收是因為向上轉型(子類往父類轉,會丟失子類的特殊功能)了。可以試試,用get()方法,LinkedList接收才有。
2.為什麽有一個實現還不夠,還弄兩個呢,它們總有區別吧?
答:ArrayDeque是基於頭尾指針來實現的Deque,意味著不能訪問除第一個和最後一個元素。想訪問得用叠代器,可以正反叠代。
ArrayDeque一般優於鏈表隊列/雙端隊列,有限數量的垃圾產生(舊數組將被丟棄在擴展),建議使用deque,ArrayDeque優先。
Set
Set集合是無序集合,集合中的元素不可以重復,訪問集合中的元素只能根據元素本身來訪問(也是不能集合裏元素不允許重復的原因)。
HashSet集合:底層數據結構是哈希表(是一個元素為鏈表的數組)
如何正確地重寫某個類的 hashCode()方法和 equals()方法?
TreeSet集合:底層數據結構是紅黑樹(是一個自平衡的二叉樹);保證元素的排序方式
LinkedHashSet集合:底層數據結構由哈希表和鏈表組成。
Map
Map集合中保存Key-value對形式的元素,訪問時只能根據每項元素的key來訪問其value。
HashMap類
HashMap是數組+鏈表+紅黑樹(JDK1.8增加了紅黑樹部分)實現的。
當創建 HashMap 時,有一個默認的負載因子( load factor),其默認值為 0.75。這是時間和空間成本上的一種折衷:增大負載因子可以減少 Hash 表(就是那個 Entry 數組)所占用的內存空間,
但會增加查詢數據的時間開銷,
而查詢是最頻繁的的操作 ( HashMap 的 get()與 put()方法都要用到查詢);減小負載因子會提高數據查詢的性能,但會增加 Hash 表所占用的內存空間。
package com.pb.collection; import java.util.HashMap; import java.util.Iterator; import java.util.Set; import java.util.Map.Entry; public class HashMapDemo { public static void main(String[] args) { HashMap<String, String> hashMap = new HashMap<String, String>(); hashMap.put("cn", "中國"); hashMap.put("jp", "日本"); hashMap.put("fr", "法國"); System.out.println(hashMap); System.out.println("cn:" + hashMap.get("cn")); System.out.println(hashMap.containsKey("cn")); System.out.println(hashMap.keySet()); System.out.println(hashMap.isEmpty()); hashMap.remove("cn"); System.out.println(hashMap.containsKey("cn")); //采用Iterator遍歷HashMap Iterator it = hashMap.keySet().iterator(); while(it.hasNext()) { String key = (String)it.next(); System.out.println("key:" + key); System.out.println("value:" + hashMap.get(key)); } //遍歷HashMap的另一個方法 Set<Entry<String, String>> sets = hashMap.entrySet(); for(Entry<String, String> entry : sets) { System.out.print(entry.getKey() + ", "); System.out.println(entry.getValue()); } } }HashMap
LinkedHashMap類
Hashtable
TreeMap類
對於 TreeMap 而言,它采用一種被稱為“紅黑樹”的排序二叉樹來保存 Map中每個Entry—每個 Entry 都被當成“紅黑樹”的一個節點對待。
Iterator(叠代器)
叠代器是一種設計模式,它是一個對象,它可以遍歷並選擇序列中的對象,而開發人員不需要了解該序列的底層結構。叠代器通常被稱為“輕量級”對象,因為創建它的代價小。
Java中的Iterator功能比較簡單,並且只能單向移動:
(1) 使用方法iterator()要求容器返回一個Iterator。第一次調用Iterator的next()方法時,它返回序列的第一個元素。註意:iterator()方法是java.lang.Iterable接口,被Collection繼承。
(2) 使用next()獲得序列中的下一個元素。
(3) 使用hasNext()檢查序列中是否還有元素。
(4) 使用remove()將叠代器新返回的元素刪除。
Iterator是Java叠代器最簡單的實現,為List設計的ListIterator具有更多的功能,它可以從兩個方向遍歷List,也可以從List中插入和刪除元素。
叠代器應用:
list l = new ArrayList(); l.add("aa"); l.add("bb"); l.add("cc"); for (Iterator iter = l.iterator(); iter.hasNext();) { String str = (String)iter.next(); System.out.println(str); } //叠代器用於while循環 Iterator iter = l.iterator(); while(iter.hasNext()){ String str = (String) iter.next(); System.out.println(str); }
總結
關於集合中List、Map、Set這三個的總結如下:
List:List和數組類似,可以動態增長,根據實際存儲的數據的長度自動增長List的長度。查找元素效率高,插入刪除效率低,因為會引起其他元素位置改變 。
ArrayList:非線程安全,適合隨機查找和遍歷,不適合插入和刪除。
LinkedList : 非線程安全,適合插入和刪除,不適合查找。
Vector : 線程安全。不過不推薦。
Map:一個key到value的映射的類 。
HashMap:非線程安全,鍵和值都允許有null值存在。
TreeMap:非線程安全,按自然順序或自定義順序遍歷鍵(key)。
LinkedHashMap:非線程安全,維護一個雙鏈表,可以將裏面的數據按寫入的順序讀出。寫入比HashMap強,新增和刪除比HashMap差。
Hashtable:線程安全,鍵和值不允許有null值存在。不推薦使用。
ConcurrentHashMap:線程安全,Hashtable的升級版。推薦多線程使用。
Set:不允許重復的數據 。檢索效率低下,刪除和插入效率高。
HashSet: 非線程安全、無序、數據可為空。
TreeSet: 非線程安全、有序、數據不可為空。
LinkedHashSet:非線程安全、無序、數據可為空。寫入比HashSet強,新增和刪除比HashSet差。
雖然集合號稱存儲的是 Java 對象,但實際上並不會真正將 Java 對象放入 Set 集合中,而只是在 Set 集合中保留這些對象的引用而已。
也就是說,Java 集合實際上是多個引用變量所組成的集合,這些引用變量指向實際的 Java 對象。
就像引用類型的數組一樣,當把 Java 對象放入數組之時,並不是真正把 Java 對象放入數組中,而只是把對象的引用放入數組中,每個數組元素都是一個引用變量。
對於每個 Java 集合來說,其實它只是多個引用變量的集合.
資料
http://www.cnblogs.com/Java3y/p/8782788.html
https://www.cnblogs.com/Java3y/p/8808818.html
()
Java入門系列(七)Java 集合框架(JCF, Java Collections Framework)