什麼情況用ArrayList or LinkedList呢?
ArrayList 和LinkedList 是Java 集合框架中用來儲存對象引用列表的兩個類別。 ArrayList 和 LinkedList 都實作 List 介面。先對List做一個簡單的了解:
列表(list)是元素的有序集合,也稱為序列。它提供了基於元素位置的操作,有助於快速存取、新增和刪除清單中特定索引位置的元素。 List 介面實作了 Collection 和 Iterable 作為父介面。它允許儲存重複值和空值,支援透過索引存取元素。
讀完這篇文章要搞清楚的問題:ArrayList和LinkedList有什麼不同之處?什麼時候該用ArrayList什麼時候又該用LinkedList呢?
(推薦影片:java影片教學#)
下面以增加和刪除元素為例比較ArrayList和LinkedList的不同之處
增加元素到列表尾端:
在ArrayList中增加元素到佇列尾端的程式碼如下:
public boolean add(E e){ ensureCapacity(size+1);//确保内部数组有足够的空间 elementData[size++]=e;//将元素加入到数组的末尾,完成添加 return true; }
ArrayList中add()方法的效能決定於ensureCapacity()方法。 ensureCapacity()的實作如下:
public vod ensureCapacity(int minCapacity){ modCount++; int oldCapacity=elementData.length; if(minCapacity>oldCapacity){ //如果数组容量不足,进行扩容 Object[] oldData=elementData; int newCapacity=(oldCapacity*3)/2+1; //扩容到原始容量的1.5倍 if(newCapacitty<minCapacity) //如果新容量小于最小需要的容量,则使用最小 //需要的容量大小 newCapacity=minCapacity ; //进行扩容的数组复制 elementData=Arrays.copyof(elementData,newCapacity); } }
可以看到,只要ArrayList的目前容量夠大,add()操作的效率非常高的。只有當ArrayList對容量的需求超出目前陣列大小時,才需要進行擴容。擴容的過程中,會進行大量的陣列複製操作。而陣列複製時,最終會呼叫System.arraycopy()方法,因此add()操作的效率還是相當高的。
LinkedList 的add()運算實作如下,它也將任意元素增加到佇列的尾端:
public boolean add(E e){ addBefore(e,header);//将元素增加到header的前面 return true; }
其中addBefore()的方法實作如下:
private Entry<E> addBefore(E e,Entry<E> entry){ Entry<E> newEntry = new Entry<E>(e,entry,entry.previous); newEntry.provious.next=newEntry; newEntry.next.previous=newEntry; size++; modCount++; return newEntry; }
可見,LinkeList由於使用了鍊錶的結構,因此不需要維護容量的大小。從這一點上來說,它比ArrayList有一定的效能優勢,然而,每次的元素增加都需要新建一個Entry對象,並進行更多的賦值操作。在頻繁的系統呼叫中,對效能會產生一定的影響。
增加元素到列表任意位置
除了提供元素到List的尾端,List介面也提供了在任意位置插入元素的方法:void add(int index,E element);
由於實現的不同,ArrayList和LinkedList在這個方法上存在一定的性能差異,由於ArrayList是基於數組實現的,而數組是一塊連續的內存空間,如果在數組的任意位置插入元素,必然導致在該位置後的所有元素需要重新排列,因此,其效率相對會比較低。
以下程式碼是ArrayList中的實作:
public void add(int index,E element){ if(index>size||index<0) throw new IndexOutOfBoundsException( "Index:"+index+",size: "+size); ensureCapacity(size+1); System.arraycopy(elementData,index,elementData,index+1,size-index); elementData[index] = element; size++; }
可以看到每次插入操作,都會進行一次陣列複製。而這個操作在增加元素到List尾端的時候是不存在的,大量的陣列重組操作會導致系統效能低。且插入元素在List中的位置越是靠前,陣列重組的開銷也越大。
而LinkedList此時顯示了優勢:
public void add(int index,E element){ addBefore(element,(index==size?header:entry(index))); }
可見,對LinkedList來說,在List的尾端插入資料與在任意位置插入資料是一樣的,不會因為插入的位置靠前而導致插入的方法性能降低。
刪除任意位置元素
對於元素的刪除,List介面提供了在任意位置刪除元素的方法:
public E remove(int index);
對ArrayList來說,remove()方法和add()方法是雷同的。在任意位置移除元素後,都要進行陣列的重組。 ArrayList的實作如下:
public E remove(int index){ RangeCheck(index); modCount++; E oldValue=(E) elementData[index]; int numMoved=size-index-1; if(numMoved>0) System.arraycopy(elementData,index+1,elementData,index,numMoved); elementData[--size]=null; return oldValue; }
可以看到,在ArrayList的每一次有效的元素刪除操作後,都要進行陣列的重組。且刪除的位置越前,陣列重組時的開銷越大。
public E remove(int index){ return remove(entry(index)); } private Entry<E> entry(int index){ if(index<0 || index>=size) throw new IndexOutBoundsException("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; }
在LinkedList的實作中,首先要透過循環找到要刪除的元素。若要刪除的位置處於List的前半段,則從前往後找;若其位置處於後半段,則從後往前找。因此無論要刪除較為靠前或靠後的元素都是非常有效率的;但要移除List中間的元素卻幾乎要遍歷完半個List,在List擁有大量元素的情況下,效率很低。
容量參數
容量參數是ArrayList和Vector等基於陣列的List的獨特效能參數。它表示初始化的陣列大小。當ArrayList所儲存的元素數量超過其已有大小時。它會進行擴容,數組的擴容會導致整個數組進行一次記憶體複製。因此合理的數組大小有助於減少數組擴容的次數,從而提高系統效能。
public ArrayList(){ this(10); } public ArrayList (int initialCapacity){ super(); if(initialCapacity<0) throw new IllegalArgumentException("Illegal Capacity:"+initialCapacity) this.elementData=new Object[initialCapacity]; }
ArrayList提供了一個可以製定初始陣列大小的建構子:
public ArrayList(int initialCapacity)
现以构造一个拥有100万元素的List为例,当使用默认初始化大小时,其消耗的相对时间为125ms左右,当直接制定数组大小为100万时,构造相同的ArrayList仅相对耗时16ms。
遍历列表
遍历列表操作是最常用的列表操作之一,在JDK1.5之后,至少有3中常用的列表遍历方式:
● forEach操作
● 迭代器
● for循环。
String tmp; long start=System.currentTimeMills(); //ForEach for(String s:list){ tmp=s; } System.out.println("foreach spend:"+(System.currentTimeMills()-start)); start = System.currentTimeMills(); for(Iterator<String> it=list.iterator();it.hasNext();){ tmp=it.next(); } System.out.println("Iterator spend;"+(System.currentTimeMills()-start)); start=System.currentTimeMills(); int size=;list.size(); for(int i=0;i<size;i++){ tmp=list.get(i); } System.out.println("for spend;"+(System.currentTimeMills()-start));
构造一个拥有100万数据的ArrayList和等价的LinkedList,使用以上代码进行测试,测试结果:
什么情况用ArrayList or LinkedList呢?
可以看到,最简便的ForEach循环并没有很好的性能表现,综合性能不如普通的迭代器,而是用for循环通过随机访问遍历列表时,ArrayList表项很好,但是LinkedList的表现却无法让人接受,甚至没有办法等待程序的结束。这是因为对LinkedList进行随机访问时,总会进行一次列表的遍历操作。性能非常差,应避免使用。
总结
ArrayList和LinkedList在性能上各有优缺点,都有各自所适用的地方,总的说来可以描述如下:
1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。
对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;
而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
3.LinkedList不支持高效的随机元素访问。
4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
本文来自php中文网,java教程栏目,欢迎学习!
以上是java中什麼情況下使用ArrayList和LinkedList?的詳細內容。更多資訊請關注PHP中文網其他相關文章!