1. 程式人生 > >Java入門系列(七)Java 集合框架(JCF, Java Collections Framework)

Java入門系列(七)Java 集合框架(JCF, Java Collections Framework)

後者 try 其他 使用 元素 下一個 erl work st2

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(
"World!"); queue.offer("你好!"); System.out.println("隊列長度:"+queue.size()); String str; while((str=queue.poll())!=null){ System.out.println(str); } System.out.println("隊列長度:"+queue.size()); }
Queue 隊列

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)