java_集合體系之List體系總結、應用場景——07
摘要:
總結很重要、他能客觀的展現出你對這個體系的了解程度、首先要對整體的結構架構要掌握、再細化到每個分支的特點、再比較不同分支之間的相同點、不同點、再根據他們不同的特性分析他們的應用場景。
一:List的整體架構圖
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2QvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2LcVDayMmZW1GZop0MZZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DM0EjMxUjM5ADNyITMzEDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
線條簡單說明:
1、上圖中虛線且無依賴字樣、說明是直接實作的接口
2、虛線但是有依賴字樣、說明此類依賴與接口、但不是直接實作接口
3、實線是繼承關系、類繼承類、接口繼承接口
類或接口說明:
1、Collection:高度抽象出來的集合、定義某一類集合所具有的基本的方法、标準。
2、Iterable:辨別性接口、要求子類提供擷取Iterator方法、并且要實作Iterator具有的幾個方法。
3、Iterator:疊代器、用于疊代Collection中元素、要求子類必須實作擷取Iterator的方法、
4、ListIterator:用于疊代List集合的疊代器、要求List子類必須實作擷取ListIterator方法、并且實作其必須方法。
5、List:以隊列的形式存儲、操作元素、定義了這種形式的集合所具有的基本方法、以及方法的定義。要求List實作類集合中每個元素都有索引、索引值從0開始、
6、Queue:以隊列的資料結構存儲、操作元素、Queue對于插入、提取和檢查操作。每個方法都存在兩種形式:一種抛出異常(操作失敗時),另一種傳回一個特殊值(null 或 false,具體取決于操作)。
7、Deque:實作Queue、使子類可以以雙向連結清單的資料結構形式存儲、操作資料、從這可以看出其子類的靈活性較大。
8、Enumeration:枚舉、用于Vector及其子類疊代元素、他避免了fail-fast機制、使得Vector及其子類在疊代元素的時候可以保證線程安全。
9、AbstractCollection:Collection的實作類、要求需要實作Collection接口的類都必須從它繼承、目的是用于簡化程式設計。
10、 AbstractList:繼承AbstractCollection、實作List接口中定義方法、目的也是簡化程式設計、并且其内部提供了擷取Iterator、ListIterator的方法。
11、 AbstractSequencedList:繼承AbstractList、使得List支援有序隊列、比如連結清單形式存儲操作元素。
12、 ArrayList:繼承AbstractList、以動态數組的形式存儲、操作元素、
13、 LinkedList:繼承AbstractSequencedList、實作Deque、List接口、以雙向連結清單的形式存儲、操作元素。
14、 Vector:繼承AbstractList、以動态數組的形式存儲、操作元素、線程安全
15、 Stack:繼承Vector、在Vector的基礎上新增以棧的形式存儲、操作元素。
二:LinkedList與ArrayList
1、相同之處
a)都直接或者間接繼承了AbstractList、都支援以索引的方式操作元素
b)都不必擔心容量問題、ArrayList是通過動态數組來儲存資料的、當容量不足時、數組會自動擴容、而LinkedList是以雙向連結清單來儲存資料的、不存在容量不足的問題
c) 都是線程不安全的、一般用于單線程的環境下、要想在并發的環境下使用可以使用Collections工具類包裝。
2、不同之處
a)ArrayList是通過動态數組來儲存資料的、而LinkedList是以雙向連結清單來儲存資料的
b)相對與ArrayList而言、LinkedList實作了Deque接口、Deque繼承了Queue接口、同時LinkedList繼承了AbstractSequencedList類、使得LinkedList在保留使用索引操作元素的功能的同時、也實作了雙向連結清單所具有的功能、這就決定了LinkedList的特定
c)對集合中元素進行不同的操作效率不同、LinkedList善于删除、添加元素、ArrayList善于查找元素。本質就是不同資料結構之間差異。
三:ArrayList與Vector
1、相同之處:
a) 都是繼承AbstractList、擁有相同的方法的定義、
b)内部都是以動态數組來存儲、操作元素的、并且都可以自動擴容。
2、不同之處:
a) 線程安全:ArrayList是線程不安全的、适用于單線程的環境下、Vector是線程安全的、使用與多線程的環境下。
b)構造方法:Vector有四個構造方法、比ArrayList多一個可以指定每次擴容多少的構造方法
c) 擴容問題:每當動态數組元素達到上線時、ArrayList擴容為:“新的容量”=“(原始容量x3)/2 + 1”、 而Vector的容量增長與“增長系數有關”,若指定了“增長系數”,且“增長系數有效(即,大于0)”;那麼,每次容量不足時,“新的容量”=“原始容量+增長系數”。若增長系數無效(即,小于/等于0),則“新的容量”=“原始容量 x 2”。
d) 效率問題:因為Vector要同步方法、這個是要消耗資源的、是以效率會比較低下
e)Vector為擺脫fail-fast機制、自己内部多提供了一種疊代方法Enumeration、
四:LinkedList、ArrayList、Vector、Stack、Array
1、不同操作的效率對比
關于上面四個集合加一個數組、在這裡給出一個表格用于表示他們的不同的操作的效率的排名、這樣更直覺、
實作機制 | 随機通路 | 疊代操作 | 插入操作 | 删除操作 | |
數組 | 連續記憶體區保護元素 | 1 | 不支援 | 不支援 | 不支援 |
ArrayList | 以數組儲存元素 | 2 | 2 | 2 | 2 |
Vector | 以數組儲存元素 | 3 | 3 | 3 | 3 |
Stack | 以數組儲存元素 | 3 | 3 | 3 | 3 |
LinkedList | 以連結清單儲存元素 | 4 | 1 | 1 | 1 |
通過執行個體來驗證上面表格的内容、由于數組比較特殊、他是犧牲的長度的變化直接在記憶體中開辟空間來存儲元素、是以查詢效率是毋庸置疑的、同時由于size一旦确定就不能改變、是以插入删除不支援。是以下面驗證沒有關于Array的的驗證
2、示例:
package com.chy.collection.example;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
import java.util.Vector;
public class EfficiencyTest {
private static ArrayList<String> arrayList = new ArrayList<String>();
private static Vector<String> vector = new Vector<String>();
private static Stack<String> stack = new Stack<String>();
private static LinkedList<String> linkedList = new LinkedList<String>();
/**
* 測試插入方法(每次都将新增加的元素插入到集合開始處)、注意不要寫成 add(Object o)方法、具體原因自己分析
*/
private static void testInsert(){
testInsert(arrayList);
testInsert(vector);
testInsert(stack);
testInsert(linkedList);
}
/**
* 測試随機通路效率
*/
private static void testRandomAccess(){
testRandomAccess(arrayList);
testRandomAccess(vector);
testRandomAccess(stack);
testRandomAccess(linkedList);
}
/**
* 測試Iterator疊代效率
*/
private static void testIterator(){
testIterator(arrayList);
testIterator(vector);
testIterator(stack);
testIterator(linkedList);
}
/**
* 測試删除效率
*/
private static void testDelete(){
testDelete(arrayList);
testDelete(vector);
testDelete(stack);
testDelete(linkedList);
}
private static void testInsert(List<String> list){
long start = currentTime();
for (int i = 0; i < 10000; i++) {
list.add(0,"a");
}
long end = currentTime();
System.out.println("the add method of " + list.getClass().getName() + " use time : " + (end - start) + "ms");
}
private static void testRandomAccess(List<String> list){
long start = currentTime();
for (int i = 0; i < list.size(); i++) {
list.get(i);
}
long end = currentTime();
System.out.println("the random access method of " + list.getClass().getName() + " use time : " + (end - start) + "ms");
}
private static void testIterator(List<String> list){
long start = currentTime();
Iterator<String> it = list.iterator();
while(it.hasNext()){
it.next();
}
long end = currentTime();
System.out.println("the iterator method of " + list.getClass().getName() + " use time : " + (end - start) + "ms");
}
private static void testDelete(List<String> list){
long start = currentTime();
for (int i = 0; i < 10000; i++) {
if(!list.isEmpty()){
list.remove(0);
}
}
long end = currentTime();
System.out.println("the delete method of " + list.getClass().getName() + " use time : " + (end - start) + "ms");
}
private static long currentTime(){
return System.currentTimeMillis();
}
public static void main(String[] args) {
testInsert();
System.out.println("==========================================================");
testRandomAccess();
System.out.println("==========================================================");
testIterator();
System.out.println("==========================================================");
testDelete();
}
}
運作結果:
the add method of java.util.ArrayList use time : 32ms
the add method of java.util.Vector use time : 47ms
the add method of java.util.Stack use time : 31ms
the add method of java.util.LinkedList use time : 15ms
==========================================================
the random access method of java.util.ArrayList use time : 15ms
the random access method of java.util.Vector use time : 16ms
the random access method of java.util.Stack use time : 17ms
the random access method of java.util.LinkedList use time : 47ms
==========================================================
the iterator method of java.util.ArrayList use time : 16ms
the iterator method of java.util.Vector use time : 15ms
the iterator method of java.util.Stack use time : 17ms
the iterator method of java.util.LinkedList use time : 16ms
==========================================================
the delete method of java.util.ArrayList use time : 47ms
the delete method of java.util.Vector use time : 31ms
the delete method of java.util.Stack use time : 32ms
the delete method of java.util.LinkedList use time : 15ms
不同的運作環境、差異可能比較大。
3、差異原因分析:
在這裡不會主要讨論所有的差異、而是通過源碼的方式分析LinkedList與Arraylist、ArrayList與Vector在随機通路、插入、删除元素方面的差異原因、至于疊代Iterator、他們都是用從AbstractList繼承的擷取Iterator方法、差異不大、不再比較。
ArrayList與LinkedList
a)ArrayList的随機通路效率高于LinkedList:
随機通路是通過索引去查找元素的、LinkedList關于擷取指定索引處值的源碼:
/** 擷取index處的元素*/
public E get(int index) {
return entry(index).element;
}
/** 擷取雙向連結清單LinkedList中指定位置的節點、是LinkedList實作List中通過index操作元素的關鍵*/
private Entry<E> entry(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size);
Entry<E> e = header;
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;
}
return e;
}
關于擷取指定索引處的值的源碼:
/** 檢測下标是否越界*/
private void RangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
}
/** 擷取ArrayList中索引為index位置的元素*/
public E get(int index) {
RangeCheck(index);
return (E) elementData[index];
}
對比兩者源碼可以看出、LinkedList擷取指定索引處的值是通過二分法先确定索引所在範圍之後、在逐個查找、直到找到指定索引處、并且對每個索引都是如此、相比于ArrayList直接定位到index處的值來講、無疑是非常浪費時間、消耗資源的、
b)ArrayList的插入、删除操作效率低于LinkedList的原因:
對于指定index處的插入、删除、ArrayList和LinkedList都是先通過索引查找到指定位置、然後進行下一步的插入删除操作、上面我們知道LinkedList是先通過二分法查找index範圍再确定index具體位置、但是ArrayList是直接定位到index處、為什麼LinkedList反而快?依然通過源碼找原因。
ArrayList關于指定位置的元素的插入:
/**
* 確定此ArrayList的最小容量能容納下參數minCapacity指定的容量、
* 1、minCapacity大于原來容量、則将原來的容量增加(oldCapacity * 3)/2 + 1;
* 2、若minCapacity仍然大于增加後的容量、則使用minCapacity作為ArrayList容量
* 3、若minCapacity不大于增加後的容量、則使用增加後的容量。
*/
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
/** 将指定元素添加到指定的索引處 、
* 注意:
* 1、如果指定的index大于Object[] 的size或者小于0、則抛IndexOutOfBoundException
* 2、檢測Object[]是否需要擴容
* 3、 将從index開始到最後的元素後移一個位置、
* 4、将新添加的元素添加到index去。
*/
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
ensureCapacity(size+1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
LinkedList關于指定位置的元素的插入:
/** 在index前添加節點,且節點的值為element*/
public void add(int index, E element) {
addBefore(element, (index==size ? header : entry(index)));
}
/** 擷取雙向連結清單LinkedList中指定位置的節點、是LinkedList實作List中通過index操作元素的關鍵*/
private Entry<E> entry(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size);
Entry<E> e = header;
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;
}
return e;
}
//建立節點、節點值是e、将建立的節點添加到entry之前
private Entry<E> addBefore(E e, Entry<E> entry) {
//覺得難了解的可以先花個幾分鐘看一下鍊式結構資料、最好是圖檔形式的
//建立節點實體
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
//将參照節點原來的上一個節點(即插在誰前面的)的下一個節點設定成newEntry
newEntry.previous.next = newEntry;
//将參照節點(即插在誰前面的)的前一個節點設定成newEntry
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
}
對比上面代碼可以看出來ArrayList每當插入一個元素時、都會調用System.arraycopy()将指定位置後面的所有元素後移一位、重新構造一個數組、這是比較消耗資源的、而LinkedList是直接改變index前後元素的上一個節點和下一個節點的引用、而不需要動其他的東西、是以效率很高。
ArrayList與Vector:
ArrayList、Vector都是繼承與AbstractList、并且在類結構上沒有多少差異、但是因為Vector要同步方法、是以在性能上不如ArrayList、從源碼也可以看出Vector許多方法都是使用關鍵字synchronized修飾的。不再貼源碼
總結:
學以緻用、最後總結下上述List集合體系的各個類的使用環境:
1、當需要對集合進行大量的查詢時、并且是單線程環境下使用ArrayList
2、當需要對集合進行大量添加、删除時、并且是單線程環境下使用LinkedList、
3、當多線程時、需要對集合進行大量的查詢時、可以考慮使用Vector或者Stack、但是不建議、我們可以使用多次提到的Collections類包裝。