1. 程式人生 > >Java容器學習筆記(一) 容器中基本概念及Collection介面相關知識

Java容器學習筆記(一) 容器中基本概念及Collection介面相關知識

本篇文章主要是總結了java容器中的相關知識點,包括容器層次結構、類圖結構,Collection介面的詳細資訊,以及Collection的一個重要子介面List介面的相關知識點總結。其中涉及到一些類如ArrayList、LinkedList、Vector、Stack、CopyOnWriteArrayList等的底層資料結構、實現機制及用法等的學習總結。

Java容器類庫的用途是儲存物件,根據資料結構不同將其劃分為兩個不同的概念

(1)    Collection,一個獨立元素的序列,其中List按照元素的插入順序儲存元素,而set不能有重複元素,Queue按照先進先出(FIFO)的方式來管理資料,Stack按照後進先出(LIFO)的順序管理資料。

(2)    Map,一組鍵值對(key-value)物件的序列,可以使用key來查詢value,其中key是不可以重複的,value可以重複。我們可以稱其為字典或者關聯陣列。其中HashMap是無序的,TreeMap是有序的,WeakHashMap是弱型別的,Hashtable是執行緒安全的。

下面這張圖來自於Thinking in Java Fourth Edition第十七章:

除上面圖中畫到的內容外在java.util.concurrent包中也實現了大量的執行緒安全的集合類,可以很方便的使用。如ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet等。

二.Collection介面

Ø  由集合類圖結構可以得知Collection介面是Java語言中最基本的集合介面,在JDK中沒有直接提供Collection介面的具體實現類,Collection的功能實現類主要是對它的兩個更具體的子介面List和Set的具體實現類。但是在Collection介面中定義了一套通用操作的實現方法和命名規則。

Ø  在JDK幫助文件中可以看到Collection介面以及各個子介面、各種形式實現類的說明。

Ø  對Collection介面的實現類構造方法一般至少有下面兩種:一個是void(無引數)構造方法,用於建立空的Collection物件例項;另一個是帶有一個Collection型別引數的構造方法,用於建立一個具有與其引數相同元素的Collection物件例項。例如HashSet類的構造方法有下面四種:

a)        HashSet():構造一個初始容量為16、載入因子為0.75的HashSet類的例項物件;

b)        HashSet(Collection<? extends E> c):構造一個包含指定集合物件的HashSet類的物件例項。

c)        HashSet(int initialCapacity):構造一個指定初始容量的HashSet類的例項物件。

d)        HashSet(int initialCapacity, float loadFactor):構造一個指定初始容量以及指定載入因子的HashSet類的例項物件。

Ø  Collection介面中共定義了15個通用的方法:

a)        Collection介面方法清單

a)        新增和刪除集合中的某個元素

•         boolean add(E o) : 將指定的元素追加到集合當中

•         boolean remove(Object o) : 將指定的元素從集合中刪除

b)        查詢與集合有關的資料

•         int size() : 返回此集合中元素的個數

•         boolean isEmpty() : 測試此集合是否為空

•         boolean contains(Object element) : 測試此集合中是否有該元素

•         Iterator<E> iterator() : 返回此集合中的各個元素進行迭代的迭代器

c)        對若干個元素以組為單位進行操作

•           boolean containsAll(Collection<?> c) : 判斷此集合是否包含給定的一組元素,包含返回true,否則false

•           boolean addAll(Collection<? extends E> c) : 將指定集合中的所有元素都新增到當前集合中

•           void clear() : 移除此集合中的所有元素

•           boolean removeAll(Collection<?> c) : 移除此集合中那些也包含在指定集合中的元素(求集合的差集)

•           boolean retainAll(Collection<?> c) : 僅保留此集合中那些也包含在指定集合中的元素(求集合的交集)

d)        將集合轉換成Object型別的物件陣列

•           Object[] toArray() : 返回包含此集合中所有元素的陣列

•           <T> T[] toArray(T[] a) : 返回包含此集合中所有元素的陣列;返回陣列的執行時型別與指定陣列的執行時型別相同

1.     List介面及其實現類


List介面中方法清單

List可以將元素維護在特定的序列中,並且允許一個相同元素在集合中多次出現。List介面在Collection介面的基礎上增加了大量的方法,使得可以在List中間插入和移除元素。除了Abstract類之外,在學習中比較常用的類有ArrayList(基於陣列實現),LinkedList(基於迴圈連結串列實現),Vector(基於陣列實現,執行緒安全),Stack(是Vector的子類,基於陣列實現),CopyOnWriteArrayList(基於陣列實現,執行緒安全)

List介面中提供的面向位置操作的各種方法:(集合中已有的方法略去)

•           void add(int index, E element) : 在列表的指定位置插入指定元素。

•           boolean addAll(int index, Collection<? extends E> c) : 將指定集合中的所有元素插入到集合中的指定位置。

•           E get(int index) : 返回集合中指定位置的元素。

•           int indexOf(Object o) : 返回指定物件在集合中第一次出現的索引,從0位置開始,返回-1為不存在該元素。

•           int lastIndexOf(Object O) : 返回指定物件在集合中最後一次出現的索引位置,返回-1為不存在。

•           ListIterator<E> listIterator() : 以正確的順序返回集合中元素的列表迭代器。

•           ListIterator<E> listIterator(int index) : 以正確的順序返回集合中元素的列表迭代器,從集合中指定的位置開始。

•           E remove(int index) : 移除集合中指定位置的元素。

•           E set(int index, E element) : 用指定元素替換集合中指定位置的元素。

•           List<E> subList(int fromIndex, int toIndex) : 返回集合中指定的fromIndex(包括)和toIndex(不包括)之間的部分檢視。

List介面提供了名稱為ListIterator的特殊迭代器。

List在資料結構中分別表現為陣列、向量、連結串列、堆疊、佇列等形式。

Ø  ArrayList的特點、實現機制及使用方法

a)      ArrayList特點:

ArrayList顧名思義,它是用陣列實現的一種線性表。常規陣列不具備自動遞增的功能,但是ArrayList在使用時我們不必考慮這個問題。可以直接按位置進行索引,查詢和修改速度較快,缺點是插入或者刪除速度較慢。在執行插入刪除時呼叫的是System.arraycopy方法,是一個native方法。

b)      ArrayList的實現機制:

在JDK原始碼中可以看到ArrayList總共只有兩個屬性,一個是Object陣列型別的elementData,一個是int型的size。

在構造方法中也可以看到,無參構造方法呼叫的是this(10),呼叫的帶一個引數的構造方法,預設無參構造方法分配一個size為10的陣列。按照Collection介面中定義的構造方法,它必須有一個通過其它集合物件構造自身物件的方法。這是一個相對比較簡單的線性表。並且JDK中提供了大量的比較好用的方法可以使用。該動態陣列在儲存空間不足時按照下面方法重新分配空間:

newCapacity = (oldCapacity*3)/2 + 1;

if(newCapacity < minCapacity) newCapacity = minCapacity;

c)      使用方法(ArrayList的使用方法其實是比較簡單,但是也是比較常用和好用的,個人感覺)

下面例子為了儘可能多的用到ArrayList的方法,可能看起來沒有多大意義

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ExampleForArrayList {
	public static void main(String[] args) {
		String[] str = new String[]{"My", "name", "is", "Wang", "Yan", "tao"};
		List<String> ls1 = new ArrayList<String>(10);
		//把陣列中的資料新增到ls1中
		for(int i=0; i<str.length; i++) {
			ls1.add(str[i]);
		}
		//使用ls1來構造ls2
		List<String> ls2 = new ArrayList<String>(ls1);
		System.out.println("ls2中元素的個數:" + ls2.size());
		System.out.println("is在ls2中的位置:" + ls2.indexOf("is"));
		System.out.println("Wang在ls2中最後一次出現的位置:" + ls2.lastIndexOf("Wang"));
		System.out.println("ls2中的所有元素:");
		//這裡使用iterator遍歷
		Iterator<String> it = ls2.listIterator();
		while(it.hasNext()) {
			System.out.println(it.next());
		}
		//我一般使用下面方法遍歷,或者基本的for迴圈遍歷
		for(String tmp : ls2) {
			System.out.println(tmp);
		}
	}
}

a)      LinkedList的特點:

現在發現java中類的命名真是太好了,比如這個吧,一看就知道它使用連結串列實現的。連結串列操作的優點就是插入刪除比較快,但是不能按索引直接存取,所以執行更新操作比較快,執行查詢操作比較慢。它的整體特性由於ArrayList。

b)      LinkedList實現機制:

檢視jdk原始碼可以得知每個元素在LinkedList中都是一個LinkedList.Entry的例項物件。該類定義如下:

private static class Entry<E> {
	E element;
	Entry<E> next;
	Entry<E> previous;
	Entry(E element, Entry<E> next, Entry<E> previous) {
	    this.element = element;
	    this.next = next;
	    this.previous = previous;
	}
}

在構造方法中這樣的定義:

           header.next = header.previous = header;

              也就是說LinkedList底層使用一個迴圈雙向連結串列實現的。

LinkedList實現了許多對first和last元素進行操作的方法,比如set、get、remove等。

雖然LinkedList獲取指定位置的元素時較ArrayList按索引獲取較慢,但是JDK中對get方法做了優化:

       if (index < (size >> 1)) {
            for (int i = 0; i <= index; i++)
                e = e.next;
       } else {
            for (int i = size; i > index; i--)
                e = e.previous;
       }

雖然還是順序挨個查詢,但是已經做了優化。size>>1 == size/2,移位運算要比除法運算效率高的多。

c)      LinkedList和ArrayList的使用方法類似,只是看自己的需要進行選擇了。除此之外LinkedList還實現了棧操作的所有方法。

Ø  Vector的特點、實現機制及使用方法

a)      Vector的特點:

ArrayList實現的是一種動態陣列,LinkedList是一種雙向迴圈連結串列,Vector並未在前兩者的基礎上做實現,而是直接實現了List介面。Vector中的所有方法前面都有一個synchronized關鍵字做修飾。Vector是有序可重複的。

b)      Vector的實現機制:

我暫時還不理解為什麼要實現Vector這個類,和ArrayList基本是一樣的,不一樣的是Vector是執行緒安全的,但是Collections裡面提供了將非執行緒安全的集合轉換成執行緒安全的集合的方法。

c)      Vector的使用方法(與ArrayList使用方法類似)

Ø  Stack的特點、實現機制及使用方法

a)      Stack的特點:

Stack(棧)是一種後進先出的序列,主要操作有判空、壓棧、退棧、取棧頂元素等。

b)      Stack的實現機制:

Stack繼承自Vector,同樣使用陣列儲存資料,根據該資料結構的特點進行了限制性操作。JDK中共提供了6個方法用於實現特定要求的操作:

•           Stack() : 構造一個空的棧

•           empty() : 判斷棧是否為空

•           peek() : 檢視棧頂元素並返回棧頂物件

•           pop() : 刪除棧頂元素並返回棧頂物件

•           push(E element) : 將一個元素壓入當前棧中

•           search(Object o) : 檢視指定物件是否在當前棧中

c)      Stack的使用方法

import java.util.Stack;
public class ExampleForStack {
	/*
	 * 這是一個非常簡單的例子
	 * 用於展現棧的這種後進先出的特性
	 * 逆序列印一個字串
	 */
	public static void main(String[] args) {
		String str = "abcdefghijklmnopqrstuvwxyz";
		Stack<Character> stack = new Stack<Character>();
		for(char ch : str.toCharArray()) {
			stack.push(ch);
		}
		while(!stack.empty()) {
			System.out.print(stack.pop());
		}
	}
}

Ø  CopyOnWriteArrayList的特點、實現機制及使用方法

a)      CopyOnWriteArrayList的特點:

CopyOnWriteArrayList是java.util.concurrent包中的一個類,此類是一個執行緒安全類。由於用到了ReentrantLock(重入鎖)同步,所以在修改效率上較ArrayList差。

b)      CopyOnWriteArrayList的實現機制:

剛開始覺得這個名字好長,並且感覺奇怪,為什麼要這樣命名?首先這是一個為了實現併發同步而設計的類,那麼在所有與修改方法相關的地方均會使用lock來保證同步。Copy-on-write的英文釋義是“寫時拷貝、寫時複製”,現在看來覺得這個名字就更容易理解了,那麼這個類到底是怎麼實現的呢?面試官說:“踏踏實實看原始碼”。-_-|||

首先說一下寫方法:

•           public E set(int index, E element) : 將指定位置的元素使用element替換掉。JDK中的原始碼如下:

public E set(int index, E element) {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
		Object[] elements = getArray();
		Object oldValue = elements[index];
		if (oldValue != element) {
			int len = elements.length;
			Object[] newElements = Arrays.copyOf(elements, len);
			newElements[index] = element;
			setArray(newElements);
		} else {
			setArray(elements);
		}
		return (E)oldValue;
	} finally {
		lock.unlock();
	}
}

在原始碼中可以看出,首先執行寫入(包括set,add,remove等)操作時,首先得到一把當前物件的重入鎖,其次獲得當前物件元素的一個拷貝(寫時拷貝),再次用修改後的元素替換掉原來的元素,最終釋放鎖。

這裡引用兩個常識:

1、JAVA中“=”操作只是將引用和某個物件關聯,假如同時有一個執行緒將引用指向另外一個物件,一個執行緒獲取這個引用指向的物件,那麼他們之間不會發生ConcurrentModificationException,他們是在虛擬機器層面阻塞的,而且速度非常快,幾乎不需要CPU時間。

2、JAVA中兩個不同的引用指向同一個物件,當第一個引用指向另外一個物件時,第二個引用還將保持原來的物件。

•           public void add(E e) : 向當前物件中加入指定元素。實現方式與set相同,均是copy-on-write。

•           還有remove等修改內容的操作。

除寫方法(修改,刪除,新增)外,CopyOnWriteArrayList類還提供了ArrayList相類似和功能更齊全的方法供選擇使用。

•           public ListIterator<E> listIterator() : 該方法返回一個ListIterator型別的迭代器,但是該迭代器的真正型別是COWIterator型別的,不允許有插入、刪除、新增等方法。

使用方法與ArrayList類似,只是用時選擇的問題。因為在寫操作時大量使用了System.arrayCopy方法,所以在效率上會有所降低。因此它適合使用在讀操作遠遠大於寫操作的場景中。