本篇博客主要講解List接口的三個實現類ArrayList、LinkedList、Vector的使用方法以及三者之間的區別。java
注意:本文中代碼使用的JDK版本爲1.8.0_191面試
ArrayList是List接口最經常使用的實現類,內部經過數組來實現,所以它的優勢是適合隨機查找和遍歷,缺點是不適合插入和刪除。segmentfault
ArrayList類的代碼聲明以下所示:數組
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
}
複製代碼
使用ArrayList添加元素有如下兩個重載:安全
boolean add(E e);
void add(int index, E element);
複製代碼
boolean add(E e);
是將元素添加到集合的末尾,微信
void add(int index, E element);
是將元素添加到指定的索引位置(索引是從0開始的)。多線程
使用方法以下所示:併發
List<String> platformList = new ArrayList<>();
// 添加元素
platformList.add("博客園");
platformList.add("掘金");
platformList.add("微信公衆號");
// 添加劇復元素,會添加成功,由於List支持添加劇復元素
platformList.add("博客園");
platformList.add("掘金");
platformList.add(3, "我的博客");
複製代碼
獲取ArrayList中指定索引處的元素的使用方法以下所示:dom
System.out.println("索引爲3的元素爲:" + platformList.get(3));
複製代碼
若是指定的索引超出了集合的最大索引,好比platformList.get(6)
就會拋出異常java.lang.IndexOutOfBoundsException
:函數
獲取ArrayList元素個數的使用方法以下所示:
System.out.println("platformList的元素個數爲:" + platformList.size());
複製代碼
使用ArrayList刪除元素有如下兩個重載:
E remove(int index);
boolean remove(Object o);
複製代碼
E remove(int index);
是刪除集合中指定索引處的元素,boolean remove(Object o);
是刪除集合中的指定元素。
使用方法以下所示:
// 指定索引刪除重複的元素 "博客園" "掘金"
platformList.remove(4);
platformList.remove(4);
// 刪除指定元素 "我的博客"
platformList.remove("我的博客");
複製代碼
修改ArrayList中指定索引處的元素值的使用方法以下所示:
platformList.set(0, "博客園:https://www.cnblogs.com/zwwhnly/");
platformList.set(1, "掘金:https://juejin.im/user/5c7ce730f265da2dca388167");
platformList.set(2, "微信公衆號:申城異鄉人");
複製代碼
判斷ArrayList是否爲空的使用方法以下所示:
System.out.println("isEmpty:" + platformList.isEmpty());
複製代碼
遍歷ArrayList的元素主要有如下3種方式:
使用方法以下所示:
System.out.println("使用Iterator遍歷:");
Iterator<String> platformIterator = platformList.iterator();
while (platformIterator.hasNext()) {
System.out.println(platformIterator.next());
}
System.out.println();
System.out.println("使用for循環遍歷:");
for (int i = 0; i < platformList.size(); i++) {
System.out.println(platformList.get(i));
}
System.out.println();
System.out.println("使用foreach遍歷:");
for (String platform : platformList) {
System.out.println(platform);
}
複製代碼
清空ArrayList中全部元素的使用方法以下所示:
platformList.clear();
複製代碼
上面講解的幾點,完整代碼以下所示:
public static void main(String[] args) {
List<String> platformList = new ArrayList<>();
// 添加元素
platformList.add("博客園");
platformList.add("掘金");
platformList.add("微信公衆號");
// 添加劇復元素,會添加成功,由於List支持添加劇復元素
platformList.add("博客園");
platformList.add("掘金");
platformList.add(3, "我的博客");
System.out.println("索引爲3的元素爲:" + platformList.get(3));
System.out.println("platformList的元素個數爲:" + platformList.size());
// 指定索引刪除重複的元素 "博客園" "掘金"
platformList.remove(4);
platformList.remove(4);
// 刪除指定元素 "我的博客"
platformList.remove("我的博客");
System.out.println("platformList的元素個數爲:" + platformList.size());
platformList.set(0, "博客園:https://www.cnblogs.com/zwwhnly/");
platformList.set(1, "掘金:https://juejin.im/user/5c7ce730f265da2dca388167");
platformList.set(2, "微信公衆號:申城異鄉人");
System.out.println("isEmpty:" + platformList.isEmpty());
System.out.println("使用Iterator遍歷:");
Iterator<String> platformIterator = platformList.iterator();
while (platformIterator.hasNext()) {
System.out.println(platformIterator.next());
}
System.out.println();
System.out.println("使用for循環遍歷:");
for (int i = 0; i < platformList.size(); i++) {
System.out.println(platformList.get(i));
}
System.out.println();
System.out.println("使用foreach遍歷:");
for (String platform : platformList) {
System.out.println(platform);
}
System.out.println();
// 清空集合
platformList.clear();
System.out.println("isEmpty:" + platformList.isEmpty());
}
複製代碼
輸出結果爲:
索引爲3的元素爲:我的博客
platformList的元素個數爲:6
platformList的元素個數爲:3
isEmpty:false
使用Iterator遍歷:
微信公衆號:申城異鄉人
使用for循環遍歷:
微信公衆號:申城異鄉人
使用foreach遍歷:
微信公衆號:申城異鄉人
isEmpty:true
LinkedList也是List接口的實現類,內部使用鏈表結構存儲數據,所以它的優勢是適合動態插入和刪除元素,缺點是隨機查找和遍歷速度比較慢。
LinkedList類的代碼聲明以下所示:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
}
複製代碼
LinkedList類的使用方法和ArrayList基本同樣,只需修改下聲明處的代碼便可:
List<String> platformList = new LinkedList<>();
複製代碼
Vector也是List接口的實現類,內部也是經過數組來實現。
Vector類的代碼聲明以下所示:
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
}
複製代碼
與ArrayList不一樣的是,Vector是線程安全的,即某一時刻只有一個線程可以寫Vector,避免多線程同時寫而引發的不一致性。不過這也形成Vector的缺點:實現線程的同步須要額外的花費,所以它的訪問速度會比ArrayList慢一些。
能夠認爲Vector是ArrayList在多線程環境下的實現版本。
因此Vector類的使用方法和ArrayList基本同樣,只需修改下聲明處的代碼便可:
List<String> platformList = new Vector<>();
複製代碼
因爲要支持線程同步,所以Vector類的不少方法都有synchronized關鍵字,以下所示:
public synchronized boolean isEmpty() {
return elementCount == 0;
}
public synchronized int size() {
return elementCount;
}
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
複製代碼
ArrayList、LinkedList、Vector都實現了List接口,因此使用方式很相似,經過上面的示例也能發現這一點。
可是ArrayList、LinkedList、Vector的內部實現方式不一樣,也就致使了它們之間是有區別的。
ArrayList和Vector是基於數組實現的,LinkedList是基於雙向鏈表實現的。
這也就致使ArrayList適合隨機查找和遍歷,而LinkedList適合動態插入和刪除元素。
關於數組和雙向鏈表,這裏不作詳解,後續會單獨寫篇文章總結。
ArrayList和LinkedList是線程不安全的,Vector是線程安全的。
Vector能夠看作是ArrayList在多線程環境下的另外一種實現方式,這也致使了Vector的效率沒有ArraykList和LinkedList高。
若是要在併發環境下使用ArrayList或者LinkedList,能夠調用Collections類的synchronizedList()方法:
Collections.synchronizedList(platformList);
複製代碼
ArrayList和Vector都是使用Object類型的數組來存儲數據的,ArrayList的默認容量是0,Vector的默認容量是10。
空說無憑,咱們先看下ArrayList的使用示例:
List<String> strArrayList = new ArrayList<>();
for (int i = 0; i < 20; i++) {
strArrayList.add(String.valueOf(i));
}
複製代碼
執行的ArrayList構造函數的源碼爲:
transient Object[] elementData;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
複製代碼
再看下Vector的使用示例:
List<String> strVector = new Vector<>();
for (int i = 0; i < 30; i++) {
strVector.add(String.valueOf(i));
}
複製代碼
執行的Vector構造函數的源碼爲:
protected Object[] elementData;
protected int capacityIncrement;
public Vector() {
this(10);
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
複製代碼
當向這兩種類型中添加元素時,若容量不夠,就會進行擴容,擴容的本質是產生一個新數組,將原數組的數據複製到新數組,再將新的元素添加到新數組中,使用的方法是Arrays.copyOf()
,其中ArrayList擴容後的容量是以前的1.5倍,Vector默認狀況下擴容後的容量是以前的2倍。
仍然使用上面的ArrayList的例子:
List<String> strArrayList = new ArrayList<>();
for (int i = 0; i < 20; i++) {
strArrayList.add(String.valueOf(i));
}
複製代碼
在執行完List<String> strArrayList = new ArrayList<>();
後,此時strArrayList的容量是0,
而後在添加第1個元素時,strArrayList的容量會擴容爲容量10,
當添加第11個元素時,strArrayList的容量會擴容爲容量15,
當添加第16個元素時,strArrayList的容量會擴容爲容量22,
若是還須要擴容,依次會擴容到33-->49。
看下ArrayList的源碼,就明白爲何會這樣擴容:
private static final int DEFAULT_CAPACITY = 10;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
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);
}
複製代碼
最核心的代碼就是int newCapacity = oldCapacity + (oldCapacity >> 1);
,因此ArrayList擴容後的容量是以前的1.5倍。
再看下上面的Vector例子:
List<String> strVector = new Vector<>();
for (int i = 0; i < 30; i++) {
strVector.add(String.valueOf(i));
}
複製代碼
在執行完List<String> strVector = new Vector<>();
後,此時strVector的容量是10,
當添加第11個元素時,strVector的容量會擴容爲容量20,
當添加第21個元素時,strVector的容量會擴容爲容量40,
若是還須要擴容,依次會擴容到80-->160。
看下Vector的源碼,就明白爲何會這樣擴容:
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
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 + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
複製代碼
最核心的代碼就是int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);
,因此Vector默認狀況下擴容後的容量是以前的2倍。
ArrayList隨機查找和遍歷的效率會高一些,但動態插入和刪除元素的效率會低一些。
LinkedList動態插入和刪除元素的效率會高一些,但隨機查找和遍歷的效率會低一些。
若是須要在多線程下操做集合元素,建議使用Vector,不然的話,建議使用ArrayList。
ArrayList、LinkedList、Vector的區別和實現原理
Java進階(四十六)簡述ArrayList、Vector與LinkedList的異同點
打個小廣告,歡迎掃碼關注微信公衆號:「申城異鄉人」,按期分享Java技術乾貨,讓咱們一塊兒進步。