List 的數據結構就是一個序列,存儲內容時直接在內存中開闢一塊連續的空間,而後將空間地址與索引對應。html
如下是List集合簡易架構圖java
由圖中的繼承關係,能夠知道,ArrayList、LinkedList、Vector、Stack都是List的四個實現類。node
下面對各個實現類進行方法剖析!git
ArrayList實現了List接口,也是順序容器,即元素存放的數據與放進去的順序相同,容許放入null元素,底層經過數組實現。
除該類未實現同步外,其他跟Vector大體相同。github
在Java1.5以後,集合還提供了泛型,泛型只是編譯器提供的語法糖,方便編程,對程序不會有實質的影響。由於全部的類都默認繼承至Object,因此這裏的數組是一個Object數組,以便可以容納任何類型的對象。編程
經常使用方法介紹數組
get()方法一樣很簡單,先判斷傳入的下標是否越界,再獲取指定元素。安全
public E get(int index) { rangeCheck(index); return elementData(index); } /** * 檢查傳入的index是否越界 */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
set()方法也很是簡單,直接對數組的指定位置賦值便可。markdown
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
ArrayList添加元素有兩個方法,一個是add(E e),另外一個是add(int index, E e)。
這兩個方法都是向容器中添加新元素,可能會出現容量(capacity)不足,所以在添加元素以前,都須要進行剩餘空間檢查,若是須要則自動擴容。擴容操做最終是經過grow()方法完成的。數據結構
grow方法實現
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1);//原來的1.5倍 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
添加元素還有另一個addAll()方法,addAll()方法可以一次添加多個元素,根據位置不一樣也有兩個方法,一個是在末尾添加的addAll(Collection<? extends E> c)方法,一個是從指定位置開始插入的addAll(int index, Collection<? extends E> c)方法。
不一樣點:addAll()的時間複雜度不只跟插入元素的多少有關,也跟插入的位置相關,時間複雜度是線性增加!
remove()方法也有兩個版本,一個是remove(int index)刪除指定位置的元素;另外一個是remove(Object o),經過o.equals(elementData[index])來刪除第一個知足的元素。
須要將刪除點以後的元素向前移動一個位置。須要注意的是爲了讓GC起做用,必須顯式的爲最後一個位置賦null值。
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; //賦null值,方便GC回收 return oldValue; }
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
在上篇文章中,咱們知道LinkedList同時實現了List接口和Deque接口,也就是說它既能夠看做一個順序容器,又能夠看做一個隊列(Queue),同時又能夠看做一個棧(Stack)。
LinkedList底層經過雙向鏈表實現,經過first
和last
引用分別指向鏈表的第一個和最後一個元素,注意這裏沒有所謂的啞元(某個參數若是在子程序或函數中沒有用到,那就被稱爲啞元),當鏈表爲空的時候first
和last
都指向null。
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable { /**容量*/ transient int size = 0; /**鏈表第一個元素*/ transient Node<E> first; /**鏈表最後一個元素*/ transient Node<E> last; ...... }
/** * 內部類Node */ private static class Node<E> { E item;//元素 Node<E> next;//後繼 Node<E> prev;//前驅 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
經常使用方法介紹
get()方法一樣很簡單,先判斷傳入的下標是否越界,再獲取指定元素。
public E get(int index) { checkElementIndex(index); return node(index).item; } /** * 檢查傳入的index是否越界 */ private void checkElementIndex(int index) { if (!isElementIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
set(int index, E element)方法將指定下標處的元素修改爲指定值,也是先經過node(int index)找到對應下表元素的引用,而後修改Node中item的值。
public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; }
一樣的,add()方法有兩方法,一個是add(E e),另外一個是add(int index, E element)。
該方法在LinkedList的末尾插入元素,由於有last指向鏈表末尾,在末尾插入元素的花費是常數時間,只須要簡單修改幾個相關引用便可。
public boolean add(E e) { linkLast(e); return true; } /** * 添加元素 */ void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) //原來鏈表爲空,這是插入的第一個元素 first = newNode; else l.next = newNode; size++; modCount++; }
該方法是在指定下表處插入元素,須要先經過線性查找找到具體位置,而後修改相關引用完成插入操做。
具體分紅兩步,1.先根據index找到要插入的位置;2.修改引用,完成插入操做。
public void add(int index, E element) { checkPositionIndex(index); if (index == size) //調用add方法,直接在末尾添加元素 linkLast(element); else //根據index找到要插入的位置 linkBefore(element, node(index)); } /** * 插入位置 */ void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }
一樣的,添加元素還有另一個addAll()方法,addAll()方法可以一次添加多個元素,根據位置不一樣也有兩個方法,一個是在末尾添加的addAll(Collection<? extends E> c)方法,另外一個是從指定位置開始插入的addAll(int index, Collection<? extends E> c)方法。
裏面也for循環添加元素,addAll()的時間複雜度不只跟插入元素的多少有關,也跟插入的位置相關,時間複雜度是線性增加!
一樣的,remove()方法也有兩個方法,一個是刪除指定下標處的元素remove(int index),另外一個是刪除跟指定元素相等的第一個元素remove(Object o)。
兩個刪除操做都是,1.先找到要刪除元素的引用;2.修改相關引用,完成刪除操做。
經過下表,找到對應的節點,而後將其刪除
public E remove(int index) { checkElementIndex(index); return unlink(node(index)); }
經過equals判斷找到對應的節點,而後將其刪除
public boolean remove(Object o) { if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); return true; } } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; }
刪除操做都是經過unlink(Node<E> x)
方法完成的。這裏須要考慮刪除元素是第一個或者最後一個時的邊界狀況。
/** * 刪除一個Node節點方法 */ E unlink(Node<E> x) { // assert x != null; final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; //刪除的是第一個元素 if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } //刪除的是最後一個元素 if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }
Vector類屬於一個挽救的子類,早在jdk1.0的時候,就已經存在此類,可是到了jdk1.2以後重點強調了集合的概念,因此,前後定義了不少新的接口,好比ArrayList、LinkedList,但考慮到早期大部分已經習慣使用Vector類,因此,爲了兼容性,java的設計者,就讓Vector多實現了一個List接口,這纔將其保留下來。
在使用方面,Vector的get
、set
、add
、remove
方法實現,與ArrayList基本相同,不一樣的是Vector在方法上加了線程同步鎖synchronized
,因此,執行效率方面,會比較慢!
public synchronized E get(int index) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); return elementData(index); }
public synchronized E set(int index, E element) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
public synchronized boolean removeElement(Object obj) { modCount++; int i = indexOf(obj); if (i >= 0) { removeElementAt(i); return true; } return false; }
在 Java 中 Stack 類表示後進先出(LIFO)的對象堆棧。棧是一種很是常見的數據結構,它採用典型的先進後出的操做方式完成的;在現實生活中,手槍彈夾的子彈就是一個典型的後進先出的結構。
在使用方面,主要方法有push
、peek
、pop
。
push方法表示,向棧中添加元素
public E push(E item) { addElement(item); return item; }
peek方法表示,查看棧頂部的對象,但不從棧中移除它
public synchronized E peek() { int len = size(); if (len == 0) throw new EmptyStackException(); return elementAt(len - 1); }
pop方法表示,移除元素,並將要移除的元素方法
public synchronized E pop() { E obj; int len = size(); obj = peek(); removeElementAt(len - 1); return obj; }
關於 Java 中 Stack 類,有不少的質疑聲,棧更適合用隊列結構來實現,這使得Stack在設計上不嚴謹,所以,官方推薦使用Deque下的類來是實現棧!
一、JDK1.7&JDK1.8 源碼
二、CarpenterLee - Java集合分析
三、博客園 - 朽木 - ArrayList、LinkedList、Vector、Stack的比較
做者:炸雞可樂
出處:www.pzblog.cn