關於做者java
郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。node
文章目錄git
更多文章:github.com/guoxiaoxing…程序員
咱們將形如A1,A2,A3,A4 ... AN稱之爲一個表,大小爲0的表咱們稱之爲空表。github
經常使用的表以下:數組
數組是最爲簡單的一種表,它在查找操做是線性時間的,可是插入與刪除則潛藏額外的開銷。安全
總結起來,插入/刪除的時間時間複雜度爲O(N)。bash
鏈表由一系列節點組成,這些節點沒必要在內存中相連,每一個節點均含有表元素和到包含該元素後繼元的節點的鏈,稱爲next鏈。這樣的鏈表
稱爲單鏈表,另外,若是節點還包含指向它在鏈表中的前驅節點的鏈,則成該鏈表爲雙鏈表。數據結構
鏈表是爲了不插入/刪除帶來的額外開銷,咱們又引入了鏈表,其中,鏈表又能夠分爲單鏈表與雙鏈表。架構
棧是限制插入和刪除只能在表的末端進行的表,也就是出棧與入棧操做。
棧是爲了在指定的位置就行插入和刪除。
應用場景
平衡符號
在咱們編寫代碼的時候,常常會遇寫錯了一個符號而報錯,例如[()]是正確的,而[(])就是錯誤的。複製代碼
方法調用
咱們知道在Java中有個叫方法調用棧的東西,它會爲每一個執行的方法建立一個棧幀,用來存儲局部變量表,操做數棧,動態連接和方法出口等信息。這種場景與上面提到
的平衡符號十分類似,由於方法的調用和放回和符號的開閉很是類似。
每次調用方法都會往棧裏插入一個棧幀,方法返回是再將其出棧,若是咱們將方法設計成遞歸調用,錯誤的結束條件或者過於龐大的數據量可能會引發棧溢出。
隊列也是一種表,使用隊列時在一端進行插入而在另外一端進行刪除。
如同棧同樣,對於隊列而言任何表的實現都是合法的。
說完了關於表的基本概念,咱們來聊一聊Java中對錶的基本實現。Java中用接口List來定義表的基本功能,包括增、刪、改、查等基本操做。
List接口定義以下:
注:鏈表、棧、隊列都是表,所以對於實現了表方法的ArrayList/LinkedList都支持者三種數據結構。
public interface List<E> extends Collection<E> {
// Query Operations
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
// Modification Operations
boolean add(E e);
boolean remove(Object o);
// Bulk Modification Operations
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
// Comparison and hashing
boolean equals(Object o);
int hashCode();
// Positional Access Operations
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
// Search Operations
int indexOf(Object o);
int lastIndexOf(Object o);
// List Iterators
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
// View
List<E> subList(int fromIndex, int toIndex);
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED);
}
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
default void sort(Comparator<? super E> c) {
Collections.sort(this, c);
}
}複製代碼
關於List接口,咱們有兩個經常使用的實現類。
ArrayList提供了一種List ADT的可增加的實現,優勢在於get、set花費常量時間,缺點在於插入、刪除代價昂貴。
ArrayList是以數組爲基礎實現的線性數據結構,具體說來,它有如下特色:
ArrayList類圖以下所示:
實現瞭如下接口:
ArrayList採用數組存取元素。
//保存ArrayList數據的數組,它採用transient關鍵字標記,說明序列化ArrayList忽略掉該字段
transient Object[] elementData;
//數據數量
private int size;複製代碼
咱們接下來看看ArrayList關於增、刪、改、查的實現。
時間複雜度:O(N)
實現原理:ArrayList能夠在指定位置增長元素,增長元素後,當前元素後面的元素都要向後移動一位。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
ensureCapacityInternal(size + 1); // Increments modCount!!
//將數組elementData從index位置開始的全部元素,拷貝到elementData從index + 1位置開始的位置
//也就是index位置後面的元素所有向後移動一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
}複製代碼
ArrayList會調用ensureCapacityInternal()方法來保證數組容量的自動增加,咱們先來看看它的實現。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//ArrayList內部數組的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
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);
}
}複製代碼
能夠看到ArrayList調用grow(int minCapacity)方法來增長容量,這裏有個最小容量minCapacity,它等於當前數組大小size+1。該方法的
計算流程以下:
這裏咱們還要提到一個數組拷貝的方法,它是一個native方法。
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);複製代碼
時間複雜度:O(N)
實現原理:刪除指定位置的元素,刪除元素後,把該元素後面的全部元素向前移動一位
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
//當前刪除index後面的元素所有向前移動一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
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;
}
}複製代碼
ArrayList提供了兩種移除元素的方法,按索引移除與按對象移除,按對象移除會先去遍歷該對象的索引,而後再按索引移除。
時間複雜度:O(1)
實現原理:替換內部數組相應位置上的元素
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
public E set(int index, E element) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
}複製代碼
時間複雜度:O(1)
實現原理:根據指定索引獲取當前元素,無需額外的計算。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}
}複製代碼
LinkedList提供了一種List ADT的雙鏈表的實現。優勢在於插入、刪除操做開銷較小,缺點在於不用於作索引,get操做代價昂貴。
LinkedList基於雙向鏈表實現的,它具備以特色:
ArrayList類圖以下所示:
實現瞭如下接口:
LinkedList裏用節點來描述裏面的數據,以下所示:
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;
}
}複製代碼
能夠看到,每一個節點包含了當前的元素以及它的前驅節點和後繼節點。
LinkedList包含了三個成員變量:
//節點個數
transient int size = 0;
//第一個節點
transient Node<E> first;
//最後一個節點
transient Node<E> last;複製代碼
咱們接下來看看ArrayList關於增、刪、改、查的實現。
時間複雜度:add(E e) - O(1),add(int index, E element) - O(N)
實現原理:增長和刪除的過程都是一個針對指定節點進行前驅和後繼關係修改的過程,若是是在起始節點和末尾節點插入、整個過程無需額外的遍歷計算。
若是實在中間位置插入,則須要查找當前索引的節點。
原理圖以下:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
public boolean add(E e) {
linkLast(e);
return true;
}
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
//查找指定索引的節點
Node<E> node(int index) {
//根據index的大小決定是從起始節點,仍是從末尾節點開始查找
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
private void linkFirst(E e) {
final Node<E> f = first;
//建立一個以起始節點last爲後繼的節點
final Node<E> newNode = new Node<>(null, e, f);
//將新建立的節點從新設置爲起始節點
first = newNode;
//若是起始節點爲空,則將新節點設置爲末尾,若是不爲空,則將起始節點的前驅指向新建立的節點
if (f == null)
last = newNode;
else
f.prev = newNode;
//大小加自增1,修改字數自增1
size++;
modCount++;
}
//末尾插入元素
void linkLast(E e) {
final Node<E> l = last;
//建立一個以末尾節點last爲前驅的節點
final Node<E> newNode = new Node<>(l, e, null);
//將新建立的節點從新設置爲末尾節點
last = newNode;
//若是末尾節點爲空,則將新節點設置爲起始節點,若是不爲空,則將末尾節點的後繼指向新建立的節點
if (l == null)
first = newNode;
else
l.next = newNode;
//大小加自增1,修改字數自增1
size++;
modCount++;
}
void linkBefore(E e, Node<E> succ) {
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;
//大小加自增1,修改字數自增1
size++;
modCount++;
}
}複製代碼
咱們能夠在指定位置插入元素,若是沒有指定位置,默認在鏈表末尾插入元素。能夠看到add方法的實現是依賴於link方法來創建前驅與後繼的聯繫。
具體說來:
linkFirst
linkBefore
時間複雜度:O(1)
實現原理:增長和刪除的過程都是一個針對指定節點進行前驅和後繼關係修改的過程,整個過程無需額外的遍歷計算。
原理圖以下:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
public E remove() {
return removeFirst();
}
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
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;
}
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
E unlink(Node<E> x) {
//找出要刪除節點的前驅與後繼
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;
}
//刪除元素置空,大小加自增1,修改字數自增1
x.item = null;
size--;
modCount++;
return element;
}
}複製代碼
能夠看出,刪除的過程就是一個接觸前驅和後繼的過程。主要的有unlink方法來實現,具體說來:
unlink
這麼描述有點繞,就是刪除原來的前驅後繼關係,從新創建鏈接。
時間複雜度:O(N)
實現原理:查找指定index對應節點,替換掉節點裏的元素,set操做也有個查找過程。
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
//查找指定索引的節點
Node<E> node(int index) {
//根據index的大小決定是從起始節點,仍是從末尾節點開始查找
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
}複製代碼
時間複雜度:O(N)
實現原理:根據index的位置決定是從起始節點開始查找,仍是從末尾節點開始查找。
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
//查找指定索引的節點
Node<E> node(int index) {
//根據index的大小決定是從起始節點,仍是從末尾節點開始查找
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
}複製代碼
關於表、棧與隊列的內容咱們就講到這裏,後續有新的內容還會在這篇文章更新。