本文主要講述了ArrayList
與LinkedList
的相同以及不一樣之處,以及二者的底層實現(環境OpenJDK 11.0.10
)。java
在詳細介紹二者的底層實現以前,先來簡單看一下二者的異同。node
List
接口,都繼承了AbstractList
(LinkedList
間接繼承,ArrayList
直接繼承)ArrayList
基於Object[]
數組,LinkedList
基於LinkedList.Node
雙向鏈表ArrayList
隨機訪問能作到O(1)
,由於能夠直接經過下標找到元素,而LinkedList
須要從頭指針開始遍歷,時間O(n)
ArrayList
初始化時須要指定一個初始化容量(默認爲10),而LinkedList
不須要ArrayList
當長度不足以容納新元素的時候,會進行擴容,而LinkedList
不會ArrayList
底層底層使用Object[]
數組實現,成員變量以下:數組
private static final long serialVersionUID = 8683452581122892189L; private static final int DEFAULT_CAPACITY = 10; private static final Object[] EMPTY_ELEMENTDATA = new Object[0]; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0]; transient Object[] elementData; private int size; private static final int MAX_ARRAY_SIZE = 2147483639;
默認的初始化容量爲10,接下來是兩個空數組,供默認構造方法以及帶初始化容量的構造方法使用:安全
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else { if (initialCapacity != 0) { throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); } this.elementData = EMPTY_ELEMENTDATA; } } public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
下面再來看一些重要方法,包括:bash
add()
remove()
indexOf()/lastIndexOf()/contains()
add()
add()
方法有四個:數據結構
add(E e)
add(int index,E e)
addAll(Collection<? extends E> c)
addAll(int index, Collection<? extends E> c
add()
先來看一下add(E e)
以及add(int index,E eelment)
:併發
private void add(E e, Object[] elementData, int s) { if (s == elementData.length) { elementData = this.grow(); } elementData[s] = e; this.size = s + 1; } public boolean add(E e) { ++this.modCount; this.add(e, this.elementData, this.size); return true; } public void add(int index, E element) { this.rangeCheckForAdd(index); ++this.modCount; int s; Object[] elementData; if ((s = this.size) == (elementData = this.elementData).length) { elementData = this.grow(); } System.arraycopy(elementData, index, elementData, index + 1, s - index); elementData[index] = element; this.size = s + 1; }
add(E e)
實際調用的是一個私有方法,判斷是否須要擴容以後,直接添加到末尾。而add(int index,E element)
會首先檢查下標是否合法,合法的話,再判斷是否須要擴容,以後調用System.arraycopy
對數組進行復制,最後進行賦值並將長度加1。dom
關於System.arraycopy
,官方文檔以下:性能
一共有5個參數:測試
也就是說:
System.arraycopy(elementData, index, elementData, index + 1, s - index);
的做用是將原數組在index
後面的元素「日後挪」,空出一個位置讓index
進行插入。
addAll()
下面來看一下兩個addAll()
:
public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); ++this.modCount; int numNew = a.length; if (numNew == 0) { return false; } else { Object[] elementData; int s; if (numNew > (elementData = this.elementData).length - (s = this.size)) { elementData = this.grow(s + numNew); } System.arraycopy(a, 0, elementData, s, numNew); this.size = s + numNew; return true; } } public boolean addAll(int index, Collection<? extends E> c) { this.rangeCheckForAdd(index); Object[] a = c.toArray(); ++this.modCount; int numNew = a.length; if (numNew == 0) { return false; } else { Object[] elementData; int s; if (numNew > (elementData = this.elementData).length - (s = this.size)) { elementData = this.grow(s + numNew); } int numMoved = s - index; if (numMoved > 0) { System.arraycopy(elementData, index, elementData, index + numNew, numMoved); } System.arraycopy(a, 0, elementData, index, numNew); this.size = s + numNew; return true; } }
在第一個addAll
中,首先判斷是否須要擴容,接着也是直接調用目標集合添加到尾部。而在第二個addAll
中,因爲多了一個下標參數,處理步驟稍微多了一點:
if
裏面的System.arraycopy
index
位置上面的add()
方法都涉及到了擴容,也就是grow
方法,下面來看一下:
private Object[] grow(int minCapacity) { return this.elementData = Arrays.copyOf(this.elementData, this.newCapacity(minCapacity)); } private Object[] grow() { return this.grow(this.size + 1); } private int newCapacity(int minCapacity) { int oldCapacity = this.elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity <= 0) { if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(10, minCapacity); } else if (minCapacity < 0) { throw new OutOfMemoryError(); } else { return minCapacity; } } else { return newCapacity - 2147483639 <= 0 ? newCapacity : hugeCapacity(minCapacity); } } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) { throw new OutOfMemoryError(); } else { return minCapacity > 2147483639 ? 2147483647 : 2147483639; } }
grow()
首先經過newCapacity
計算須要擴容的容量,接着調用Arrays.copyOf
將舊元素複製過去,並將返回值覆蓋到原來的數組。而在newCapacity
中,有兩個變量:
newCapacity
:新的容量,默認是舊容量的1.5倍,也就是默認擴容1.5倍minCapacity
:最低須要的容量若是最低容量大於等於新容量,則是以下狀況之一:
minCapacity
與10的最大值OOM
若是不是,則判斷新容量是否達到最大值(這裏有點好奇爲何不用MAX_ARRAY_SIZE
,猜想是反編譯的問題),若是沒有到達最大值,則返回新容量,若是到達了最大值,調用hugeCapacity
。
hugeCapacity
一樣會首先判斷最小容量是否小於0,小於則拋OOM
,不然將其與最大值(MAX_ARRAY_SIZE
)判斷,若是大於返回Integer.MAX_VALUE
,不然返回MAX_ARRAY_SIZE
。
remove()
remove()
包含四個方法:
remove(int index)
remove(Object o)
removeAll()
removeIf()
remove()
也就是remove(int index)
以及remove(Object o)
:
public E remove(int index) { Objects.checkIndex(index, this.size); Object[] es = this.elementData; E oldValue = es[index]; this.fastRemove(es, index); return oldValue; } public boolean remove(Object o) { Object[] es = this.elementData; int size = this.size; int i = 0; if (o == null) { while(true) { if (i >= size) { return false; } if (es[i] == null) { break; } ++i; } } else { while(true) { if (i >= size) { return false; } if (o.equals(es[i])) { break; } ++i; } } this.fastRemove(es, i); return true; }
其中remove(int index)
的邏輯比較簡單,先檢查下標合法性,而後保存須要remove
的值,並調用fastRemove()
進行移除,而在remove(Object o)
中,直接對數組進行遍歷,並判斷是否存在對應的元素,若是不存在直接返回false
,若是存在,調用fastRemove()
,並返回true
。
下面看一下fastRemove()
:
private void fastRemove(Object[] es, int i) { ++this.modCount; int newSize; if ((newSize = this.size - 1) > i) { System.arraycopy(es, i + 1, es, i, newSize - i); } es[this.size = newSize] = null; }
首先修改次數加1,而後將數組長度減1,並判斷新長度是不是最後一個,若是是最後一個則不須要移動,若是不是,調用System.arraycopy
將數組向前「挪」1位,最後將末尾多出來的一個值置爲null
。
removeAll()
public boolean removeAll(Collection<?> c) { return this.batchRemove(c, false, 0, this.size); } boolean batchRemove(Collection<?> c, boolean complement, int from, int end) { Objects.requireNonNull(c); Object[] es = this.elementData; for(int r = from; r != end; ++r) { if (c.contains(es[r]) != complement) { int w = r++; try { for(; r < end; ++r) { Object e; if (c.contains(e = es[r]) == complement) { es[w++] = e; } } } catch (Throwable var12) { System.arraycopy(es, r, es, w, end - r); w += end - r; throw var12; } finally { this.modCount += end - w; this.shiftTailOverGap(es, w, end); } return true; } } return false; }
removeAll
實際上調用的是batchRemove()
,在batchRemove()
中,有四個參數,含義以下:
Collection<?> c
:目標集合boolean complement
:若是取值true
,表示保留數組中包含在目標集合c
中的元素,若是爲false
,表示刪除數組中包含在目標集合c
中的元素from/end
:區間範圍,左閉右開因此傳遞的(c,false,0,this.size)
表示刪除數組裏面在目標集合c
中的元素。下面簡單說一下執行步驟:
w
try/catch
是一種保護性行爲,由於contains()
在AbstractCollection
的實現中,會使用Iterator
,這裏catch
異常後仍然調用System.arraycopy
,使得已經處理的元素「挪到」前面shiftTailOverGap
,該方法在後面會詳解removeIf()
public boolean removeIf(Predicate<? super E> filter) { return this.removeIf(filter, 0, this.size); } boolean removeIf(Predicate<? super E> filter, int i, int end) { Objects.requireNonNull(filter); int expectedModCount = this.modCount; Object[] es; for(es = this.elementData; i < end && !filter.test(elementAt(es, i)); ++i) { } if (i < end) { int beg = i; long[] deathRow = nBits(end - i); deathRow[0] = 1L; ++i; for(; i < end; ++i) { if (filter.test(elementAt(es, i))) { setBit(deathRow, i - beg); } } if (this.modCount != expectedModCount) { throw new ConcurrentModificationException(); } else { ++this.modCount; int w = beg; for(i = beg; i < end; ++i) { if (isClear(deathRow, i - beg)) { es[w++] = es[i]; } } this.shiftTailOverGap(es, w, end); return true; } } else if (this.modCount != expectedModCount) { throw new ConcurrentModificationException(); } else { return false; } }
在removeIf
中,刪除符合條件的元素,首先會進行判空操做,而後找到第一個符合條件的元素下標,若是找不到(i>=end
),判斷是否有併發操做問題,沒有的話返回false
,若是i<end
,也就是正式進入刪除流程:
beg
deathRow
是一個標記數組,長度爲(end-i-1)>>6 + 1
,從beg
開始若是遇到符合條件的元素就對下標進行標記(調用setBit
)beg
以後的位置上shiftTailOverGap
處理末尾的元素true
,表示存在符合條件的元素並進行了刪除操做shiftTailOverGap()
上面的removeAll()
以及removeIf()
都涉及到了shiftTailOverGap()
,下面來看一下實現:
private void shiftTailOverGap(Object[] es, int lo, int hi) { System.arraycopy(es, hi, es, lo, this.size - hi); int to = this.size; for(int i = this.size -= hi - lo; i < to; ++i) { es[i] = null; } }
該方法將es
數組中的元素向前移動hi-lo
位,並將移動以後的在末尾多出來的那部分元素置爲null
。
indexOf()
系列包括:
indexOf()
lastIndexOf()
contains()
indexOf
public int indexOf(Object o) { return this.indexOfRange(o, 0, this.size); } int indexOfRange(Object o, int start, int end) { Object[] es = this.elementData; int i; if (o == null) { for(i = start; i < end; ++i) { if (es[i] == null) { return i; } } } else { for(i = start; i < end; ++i) { if (o.equals(es[i])) { return i; } } } return -1; }
indexOf()
其實是一個包裝好的方法,會調用內部的indexOfRange()
進行查找,邏輯很簡單,首先判斷須要查找的值是否爲空,若是不爲空,使用equals()
判斷,不然使用==
判斷,找到就返回下標,不然返回-1
。
contains()
contains()
其實是indexOf()
的包裝:
public boolean contains(Object o) { return this.indexOf(o) >= 0; }
調用indexOf()
方法,根據返回的下標判斷是否大於等於0,若是是則返回存在,不然返回不存在。
lastIndexOf()
lastIndexOf()
實現與indexOf()
相似,只不過是從尾部開始遍歷,內部調用的是lastIndexOfRange()
:
public int lastIndexOf(Object o) { return this.lastIndexOfRange(o, 0, this.size); } int lastIndexOfRange(Object o, int start, int end) { Object[] es = this.elementData; int i; if (o == null) { for(i = end - 1; i >= start; --i) { if (es[i] == null) { return i; } } } else { for(i = end - 1; i >= start; --i) { if (o.equals(es[i])) { return i; } } } return -1; }
LinkedList
底層首先來看一下里面的成員變量:
transient int size; transient LinkedList.Node<E> first; transient LinkedList.Node<E> last; private static final long serialVersionUID = 876323262645176354L;
一個表示長度,一個頭指針和一個尾指針。
其中LinkedList.Node
實現以下:
private static class Node<E> { E item; LinkedList.Node<E> next; LinkedList.Node<E> prev; Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
能夠看到LinkedList
實際是基於雙鏈表實現的。
下面再來看一些重要方法,包括:
add()
remove()
get()
add()
add()
方法包括6個:
add(E e)
add(int index,E e)
addFirst(E e)
addLast(E e)
addAll(Collection<? extends E> c)
addAll(int index, Collection<? extends E> c)
linkFirst
/linkLast
/linkBefore
實現的add()
先看一下比較簡單的四個add()
:
public void addFirst(E e) { this.linkFirst(e); } public void addLast(E e) { this.linkLast(e); } public boolean add(E e) { this.linkLast(e); return true; } public void add(int index, E element) { this.checkPositionIndex(index); if (index == this.size) { this.linkLast(element); } else { this.linkBefore(element, this.node(index)); } }
能夠看到,上面的四個add()
不進行任何的添加元素操做,add()
只是添加元素的封裝,真正實現add
操做的是linkLast()
、linkFirst()
和linkBefore()
,這些方法顧名思義就是把元素連接到鏈表的末尾或者頭部,或者鏈表某個節點的前面:
void linkLast(E e) { LinkedList.Node<E> l = this.last; LinkedList.Node<E> newNode = new LinkedList.Node(l, e, (LinkedList.Node)null); this.last = newNode; if (l == null) { this.first = newNode; } else { l.next = newNode; } ++this.size; ++this.modCount; } private void linkFirst(E e) { LinkedList.Node<E> f = this.first; LinkedList.Node<E> newNode = new LinkedList.Node((LinkedList.Node)null, e, f); this.first = newNode; if (f == null) { this.last = newNode; } else { f.prev = newNode; } ++this.size; ++this.modCount; } void linkBefore(E e, LinkedList.Node<E> succ) { LinkedList.Node<E> pred = succ.prev; LinkedList.Node<E> newNode = new LinkedList.Node(pred, e, succ); succ.prev = newNode; if (pred == null) { this.first = newNode; } else { pred.next = newNode; } ++this.size; ++this.modCount; }
實現大致相同,一個是添加到尾部,一個是添加頭部,一個是插入到前面。另外,三者在方法的最後都有以下操做:
++this.size; ++this.modCount;
第一個表示節點的個數加1,而第二個,則表示對鏈表的修改次數加1。
好比,在unlinkLast
方法的最後,有以下代碼:
--this.size; ++this.modCount;
unlinkLast
操做就是移除最後一個節點,節點個數減1的同時,對鏈表的修改次數加1。
另外一方面,一般來講鏈表插入操做須要找到鏈表的位置,可是在三個link
方法裏面,都看不到for
循環找到插入位置的代碼,這是爲何呢?
因爲保存了頭尾指針,linkFirst()
以及linkLast()
並不須要遍歷找到插入的位置,可是對於linkBefore()
來講,須要找到插入的位置,不過linkBefore()
並無相似「插入位置/插入下標」之類的參數,而是隻有一個元素值以及一個後繼節點。換句話說,這個後繼節點就是經過循環獲得的插入位置,好比,調用的代碼以下:
this.linkBefore(element, this.node(index));
能夠看到在this.node()
中,傳入了一個下標,並返回了一個後繼節點,也就是遍歷操做在該方法完成:
LinkedList.Node<E> node(int index) { LinkedList.Node x; int i; if (index < this.size >> 1) { x = this.first; for(i = 0; i < index; ++i) { x = x.next; } return x; } else { x = this.last; for(i = this.size - 1; i > index; --i) { x = x.prev; } return x; } }
這裏首先經過判斷下標是位於「哪一邊」,若是靠近頭部,從頭指針開始日後遍歷,若是靠近尾部,從尾指針開始向後遍歷。
addAll()
public boolean addAll(Collection<? extends E> c) { return this.addAll(this.size, c); } public boolean addAll(int index, Collection<? extends E> c) { this.checkPositionIndex(index); Object[] a = c.toArray(); int numNew = a.length; if (numNew == 0) { return false; } else { LinkedList.Node pred; LinkedList.Node succ; if (index == this.size) { succ = null; pred = this.last; } else { succ = this.node(index); pred = succ.prev; } Object[] var7 = a; int var8 = a.length; for(int var9 = 0; var9 < var8; ++var9) { Object o = var7[var9]; LinkedList.Node<E> newNode = new LinkedList.Node(pred, o, (LinkedList.Node)null); if (pred == null) { this.first = newNode; } else { pred.next = newNode; } pred = newNode; } if (succ == null) { this.last = pred; } else { pred.next = succ; succ.prev = pred; } this.size += numNew; ++this.modCount; return true; } }
首先能夠看到兩個addAll
實際上調用的是同一個方法,步驟簡述以下:
checkPositionIndex
判斷下標是否合法Object[]
數組index
的範圍是插入中間,仍是在末尾插入for
循環遍歷目標數組,並插入到鏈表中remove()
與add()
相似,remove
包括:
remove()
remove(int index)
remove(Object o)
removeFirst()
removeLast()
removeFirstOccurrence(Object o)
removeLastOccurrence(Object o)
固然其實還有兩個removeAll
與removeIf
,但其實是父類的方法,這裏就不分析了。
unlinkFirst()
/unlinkLast()
實現的remove()
remove()
、removeFirst()
、removeLast()
其實是經過調用unlinkFirst()
/unlinkLast()
進行刪除的,其中remove()
只是removeFirst()
的一個別名:
public E remove() { return this.removeFirst(); } public E removeFirst() { LinkedList.Node<E> f = this.first; if (f == null) { throw new NoSuchElementException(); } else { return this.unlinkFirst(f); } } public E removeLast() { LinkedList.Node<E> l = this.last; if (l == null) { throw new NoSuchElementException(); } else { return this.unlinkLast(l); } }
邏輯很簡單,判空以後,調用unlinkFirst()
/unlinkLast()
:
private E unlinkFirst(LinkedList.Node<E> f) { E element = f.item; LinkedList.Node<E> next = f.next; f.item = null; f.next = null; this.first = next; if (next == null) { this.last = null; } else { next.prev = null; } --this.size; ++this.modCount; return element; } private E unlinkLast(LinkedList.Node<E> l) { E element = l.item; LinkedList.Node<E> prev = l.prev; l.item = null; l.prev = null; this.last = prev; if (prev == null) { this.first = null; } else { prev.next = null; } --this.size; ++this.modCount; return element; }
而在這兩個unlink
中,因爲已經保存了頭指針和尾指針的位置,所以二者能夠直接在O(1)
內進行移除操做,最後將節點長度減1,修改次數加1,並返回舊元素。
unlink()
實現的remove()
再來看一下remove(int index)
、remove(Object o)
、removeFirstOccurrence(Object o)
、removeLastOccurrence(Object o)
:
public E remove(int index) { this.checkElementIndex(index); return this.unlink(this.node(index)); } public boolean remove(Object o) { LinkedList.Node x; if (o == null) { for(x = this.first; x != null; x = x.next) { if (x.item == null) { this.unlink(x); return true; } } } else { for(x = this.first; x != null; x = x.next) { if (o.equals(x.item)) { this.unlink(x); return true; } } } return false; } public boolean removeFirstOccurrence(Object o) { return this.remove(o); } public boolean removeLastOccurrence(Object o) { LinkedList.Node x; if (o == null) { for(x = this.last; x != null; x = x.prev) { if (x.item == null) { this.unlink(x); return true; } } } else { for(x = this.last; x != null; x = x.prev) { if (o.equals(x.item)) { this.unlink(x); return true; } } } return false; }
這幾個方法實際上都是調用unlink
去移除元素,其中removeFirstOccurrence(Object o)
等價於remove(Object o)
,先說一下remove(int index)
,該方法邏輯比較簡單,先檢查下標合法性,再經過下標找到節點並進行unlnk
。
而在remove(Object o)
中,須要首先對元素的值是否爲null
進行判斷,兩個循環實際上效果等價,會移除遇到的第一個與目標值相同的元素。在removeLastOccurrence(Object o)
中,代碼大致一致,只是remove(Object o)
從頭指針開始遍歷,而removeLastOccurrence(Object o)
從尾指針開始遍歷。
能夠看到,這幾個remove
方法其實是找到要刪除的節點,最後調用unlink()
進行刪除,下面看一下unlink()
:
E unlink(LinkedList.Node<E> x) { E element = x.item; LinkedList.Node<E> next = x.next; LinkedList.Node<E> prev = x.prev; if (prev == null) { this.first = next; } else { prev.next = next; x.prev = null; } if (next == null) { this.last = prev; } else { next.prev = prev; x.next = null; } x.item = null; --this.size; ++this.modCount; return element; }
實現邏輯與unlinkFirst()
/unlinkLast()
相似,在O(1)
內進行刪除,裏面只是一些比較簡單的特判操做,最後將節點長度減1,並將修改次數加1,最後返回舊值。
get()
get
方法比較簡單,對外提供了三個:
get(int index)
getFirst()
getLast()
其中getFirst()
以及getLast()
因爲保存了頭尾指針,特判後,直接O(1)
返回:
public E getFirst() { LinkedList.Node<E> f = this.first; if (f == null) { throw new NoSuchElementException(); } else { return f.item; } } public E getLast() { LinkedList.Node<E> l = this.last; if (l == null) { throw new NoSuchElementException(); } else { return l.item; } }
而get(int index)
毫無疑問須要O(n)
時間:
public E get(int index) { this.checkElementIndex(index); return this.node(index).item; }
get(int index)
判斷下標後,實際上進行操做的是this.node()
,因爲該方法是經過下標找到對應的節點,源碼前面也寫上了,這裏就不分析了,須要O(n)
的時間。
ArrayList
基於Object[]
實現,LinkedList
基於雙鏈表實現ArrayList
隨機訪問效率要高於LinkedList
LinkedList
提供了比ArrayList
更多的插入方法,並且頭尾插入效率要高於ArrayList
ArrayList
提供了獨有的removeIf()
,而LinkedList
提供了獨有的removeFirstOccurrence()
以及removeLastOccurrence()
ArrayList
的get()
方法始終爲O(1)
,而LinkedList
只有getFirst()
/getLast()
爲O(1)
ArrayList
中的兩個核心方法是grow()
以及System.arraycopy
,前者是擴容方法,默認爲1.5倍擴容,後者是複製數組方法,是一個native
方法,插入、刪除等等操做都須要使用LinkedList
中不少方法須要對頭尾進行特判,建立比ArrayList
簡單,無須初始化,不涉及擴容問題關於插入與刪除,一般認爲LinkedList
的效率要比ArrayList
高,但實際上並非這樣,下面是一個測試插入與刪除時間的小實驗。
相關說明:
代碼:
import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; public class Main { public static void main(String[] args){ int len = 40_0000; Random random = new Random(); List<Integer> list = Stream.generate(random::nextInt).limit(len).collect(Collectors.toList()); LinkedList<Integer> linkedList = new LinkedList<>(list); ArrayList<Integer> arrayList = new ArrayList<>(list); long start; long end; double linkedListTotalInsertTime = 0.0; double arrayListTotalInsertTime = 0.0; int testTimes = 1000; for (int i = 0; i < testTimes; i++) { int index = random.nextInt(len); int element = random.nextInt(); start = System.nanoTime(); linkedList.add(index,element); end = System.nanoTime(); linkedListTotalInsertTime += (end-start); start = System.nanoTime(); arrayList.add(index,element); end = System.nanoTime(); arrayListTotalInsertTime += (end-start); } System.out.println("LinkedList average insert time:"+linkedListTotalInsertTime/testTimes+" ns"); System.out.println("ArrayList average insert time:"+arrayListTotalInsertTime/testTimes + " ns"); linkedListTotalInsertTime = arrayListTotalInsertTime = 0.0; for (int i = 0; i < testTimes; i++) { int index = random.nextInt(len); start = System.nanoTime(); linkedList.remove(index); end = System.nanoTime(); linkedListTotalInsertTime += (end-start); start = System.nanoTime(); arrayList.remove(index); end = System.nanoTime(); arrayListTotalInsertTime += (end-start); } System.out.println("LinkedList average delete time:"+linkedListTotalInsertTime/testTimes+" ns"); System.out.println("ArrayList average delete time:"+arrayListTotalInsertTime/testTimes + " ns"); } }
在數組長度爲4000
的時候,輸出以下:
LinkedList average insert time:4829.938 ns ArrayList average insert time:745.529 ns LinkedList average delete time:3142.988 ns ArrayList average delete time:1703.76 ns
而在數組長度40w
的時候(參數-Xmx512m -Xms512m
),輸出以下:
LinkedList average insert time:126620.38 ns ArrayList average insert time:25955.014 ns LinkedList average delete time:119281.413 ns ArrayList average delete time:25435.593 ns
而將數組長度調到4000w
(參數-Xmx16g -Xms16g
),時間以下:
LinkedList average insert time:5.6048377238E7 ns ArrayList average insert time:2.5303627956E7 ns LinkedList average delete time:5.4753230158E7 ns ArrayList average delete time:2.5912990133E7 ns
雖然這個實驗有必定的侷限性,但也是證實了ArrayList
的插入以及刪除性能並不會比LinkedList
差。實際上,經過源碼(看下面分析)能夠知道,ArrayList
插入以及刪除的主要耗時在於System.arraycopy
,而LinkedList
主要耗時在於this.node()
,實際上二者須要的都是O(n)
時間。
至於爲何ArrayList
的插入和刪除速度要比LinkedList
快,筆者猜想,是System.arraycopy
的速度快於LinkedList
中的for
循環遍歷速度,由於LinkedList
中找到插入/刪除的位置是經過this.node()
,而該方法是使用簡單的for
循環實現的(固然底層是首先判斷是位於哪一邊,靠近頭部的話從頭部開始遍歷,靠近尾部的話從尾部開始遍歷)。相對於System.arraycopy
的原生C++
方法實現,可能會慢於C++
,所以形成了速度上的差別。