前言
對于一個Java開發(fā)來說,集合是無時無刻不存在的一個工具類晒喷,我們經(jīng)常使用集合存儲同一類型的數(shù)據(jù)旺嬉,當(dāng)然也可以存儲不同類型的數(shù)據(jù),具體得看集合的范型是什么厨埋。集合的總類有多種邪媳,今天我們來聊一聊List集合。
List是一個集合的接口荡陷,實現(xiàn)List接口的實現(xiàn)類都有一個特性:有序雨效,可重復(fù)。常見的實現(xiàn)類有Vector废赞、ArrayList和LinkedList徽龟,接下來我們一起討論一下他們的區(qū)別,為什么一個List接口會有這么多實現(xiàn)唉地,他們分別適用于什么場景据悔。
Vector
這是一個元老級的集合實現(xiàn) Since JDK1.0 也就是JDK誕生的時候就存在了。它相比于其他兩個實現(xiàn)類最大的區(qū)別就是Vector可以保證原子性耘沼,能保證線程安全极颓,成也蕭何拜也蕭何,正是因為它的原子性導(dǎo)致它的性能低下群嗤,這也是我們工作中不考慮使用的原因菠隆。
源碼分析
它有三個重要的屬性
/*
* 數(shù)據(jù)存儲的地方,沒錯,就是用一個數(shù)組存儲
*/
protected Object[] elementData;
/*
* 當(dāng)前容器中數(shù)據(jù)的數(shù)量骇径,也就是elementData數(shù)組存儲了多少數(shù)據(jù)
*/
protected int elementCount;
/*
* 當(dāng)數(shù)組容量不足時躯肌,擴容的大小,可通過構(gòu)造方法指定破衔,不指定默認(rèn)擴大兩倍
*/
protected int capacityIncrement;
整個容器方法的實現(xiàn)基本都是圍繞這三個參數(shù)來實現(xiàn)清女。
當(dāng)我們使用一個類時,需要通過構(gòu)造方法進行實例化晰筛,Vector提供的構(gòu)造方法如下
/*
* 無參構(gòu)造方法
*/
public Vector() {
this(10);
}
/*
* 指定初始容量大小
*/
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
/*
* 以上兩個構(gòu)造方法最終都是調(diào)用這個構(gòu)造方法實例化嫡丙,
* @param initialCapacity 初始容量大小,也就是指定數(shù)組長度传惠,比需大于0
* @param capacityIncrement 擴容增長數(shù)量
*/
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
以上構(gòu)造方法我們可以根據(jù)實際情況而定迄沫,如果我們能知道存儲數(shù)據(jù)的量,我們可以通過第二個構(gòu)造方法指定容量大小卦方,減少擴容帶來的性能問題羊瘩。
執(zhí)行構(gòu)造方法后,我們的容器就創(chuàng)建好了盼砍,接下來我們就可以往容器里放數(shù)據(jù)尘吗。
add(E e)
add方法是容器為我們提供添加數(shù)據(jù)的方法,老規(guī)矩浇坐,先看源碼
public synchronized boolean add(E e) {
modCount++;
// 擴容相關(guān)
ensureCapacityHelper(elementCount + 1);
// 插入數(shù)據(jù)
elementData[elementCount++] = e;
return true;
}
/*
* 為什么這個方法沒有使用synchronized修飾呢睬捶?
* 因為調(diào)用該方法的所有方法都使用了synchronized修飾,外部已經(jīng)保證原子* 性近刘,無需多此一舉
*/
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
// 所需容量 - 數(shù)組長度
if (minCapacity - elementData.length > 0)
grow(minCapacity); // 真正擴容的地方
}
/**
* 擴容
*/
private void grow(int minCapacity) {
// overflow-conscious code
// 原數(shù)組長度
int oldCapacity = elementData.length;
// 計算數(shù)組長度=原數(shù)組長度(默認(rèn)10) + (capacityIncrement 不指定默認(rèn)0)擒贸,所以最終新的數(shù)組長度=10+10=20
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
// 新長度驗證
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 將原數(shù)組數(shù)據(jù)復(fù)制到新的數(shù)組中
elementData = Arrays.copyOf(elementData, newCapacity);
}
首先看方法使用synchronized進行修飾,這也就是它能保證原子性的原因觉渴,基本所有的方法都加了synchronized介劫。
屬性modCount我們先不說,后邊再統(tǒng)一說明案淋。
add主要的邏輯都在代碼里寫了注釋座韵,相信聰明的你一定能看懂。
add還有另一個方法add(int index, E element)踢京,我將它和set(int index,E element)一起說誉碴,他們很相似,但又有不同之處瓣距。
add(int index, E element)
/*
* 什么也不干黔帕,之間丟給insertElementAt,這不就是套娃嘛
* 相比add方法旨涝,多了一個index參數(shù)蹬屹,我們可以通過這個方法在指定位置插入一條數(shù)據(jù)侣背,也就是插隊
*/
public void add(int index, E element) {
insertElementAt(element, index);
}
/*
* 真正干苦力的人
*/
public synchronized void insertElementAt(E obj, int index) {
modCount++;
// 如果index大于容器大小白华,之間拋異常
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
+ " > " + elementCount);
}
// 和add方法一樣慨默,擴容的地方
ensureCapacityHelper(elementCount + 1);
// 將指定位置的數(shù)據(jù)往后移動,數(shù)據(jù)量大的時候弧腥,此方法的性能是很差的
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
// 在指定位置插入數(shù)據(jù)
elementData[index] = obj;
// 容器數(shù)據(jù)量+1
elementCount++;
}
set(int index,E element)
/*
* 此方法和add方法不一樣厦取,它是對指定位置的數(shù)據(jù)進行修改,然后返回修改之前的數(shù)據(jù)
*/
public synchronized E set(int index, E element) {
// 一樣管搪,都是先判斷index
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
// 先獲取到老的數(shù)據(jù)
E oldValue = elementData(index);
// 修改成新的數(shù)據(jù)
elementData[index] = element;
return oldValue;
}
源碼分析做了注釋虾攻,他們的區(qū)別如下
add 是插入數(shù)據(jù),在指定位置插入數(shù)據(jù)更鲁,數(shù)據(jù)讓后移動
set 是修改數(shù)據(jù)霎箍,將指定位置的數(shù)據(jù)進行修改,然后返回修改前的數(shù)據(jù)
我們往容器中放數(shù)據(jù)就是為了需要使用的數(shù)據(jù)讀取出來澡为,容器為我們提供了如下幾個方法
get(int index)
/*
* 獲取指定位置的數(shù)據(jù)
*/
public synchronized E get(int index) {
// 判斷獲取數(shù)據(jù)的位置是否存在
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
// 獲取位置并返回
return elementData(index);
}
firstElement()
我們可以通過此方法獲取容器中第一條數(shù)據(jù)漂坏,很簡單,不分析了
public synchronized E firstElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(0);
}
lastElement()
獲取容器最后一條數(shù)據(jù)
public synchronized E lastElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(elementCount - 1);
}
容器能插入數(shù)據(jù)媒至,肯定也能刪除數(shù)據(jù)顶别,容器為我們提供了remove方法進行刪除,源碼太簡單了拒啰,一看就明白驯绎,這里不分析了。
modCount
在源碼中多處存在這個參數(shù)谋旦,那么它有什么作用呢剩失?
modCount是Vector的父類AbstractList里的一個屬性
用于實現(xiàn)fail-fast機制(快速失敗)册着,在并發(fā)集合中拴孤,對集合進行迭代操作,若期間有其他線程對集合的結(jié)構(gòu)進行操作指蚜,此時迭代器能快速感知到乞巧,并拋出ConcurrentModificationException異常。
protected transient int modCount = 0;
該參數(shù)主要用來標(biāo)記容器中數(shù)據(jù)變更的次數(shù)摊鸡,仔細(xì)觀察發(fā)現(xiàn)在所有的數(shù)據(jù)變更的方法中都有它的影子绽媒。
它的作用是防止我們在使用迭代器遍歷的過程中,對數(shù)據(jù)進行修改免猾,迭代器在對容器進行遍歷的時候是辕,首先會判斷此屬性的值是否有變更,有變更則拋異常猎提。
我們實現(xiàn)一個迭代器遍歷容器的案例
// 創(chuàng)建一個容器
Vector vector = new Vector();
vector.add("a");
vector.add("b");
// 創(chuàng)建迭代器
Iterator iterator = vector.iterator();
System.out.println(iterator.next());
// 新增一條數(shù)據(jù)获三,則modCount++
vector.add("c");
System.out.println(iterator.next());
輸出結(jié)果
a
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.Vector$Itr.checkForComodification(Vector.java:1210)
at java.util.Vector$Itr.next(Vector.java:1163)
at com.xsx.study.collection.VectorDemo.main(VectorDemo.java:17)
由于兩次next()方法中間修改了容器的數(shù)據(jù)
vector.iterator(); 其實是new Itr();創(chuàng)建一個Itr實例,Itr是Vector的一個內(nèi)部類,該內(nèi)部類實現(xiàn)Iterator接口
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
// 關(guān)鍵在這疙教,我們創(chuàng)建一個Itr的時候棺聊,將modCount賦值給expectedModCount,next()方法會判斷這兩個值是否相等贞谓,不相等說明容器被修改過了限佩,拋異常
int expectedModCount = modCount;
}
什么?你不信裸弦,讓你死心塌地
public E next() {
synchronized (Vector.this) {
// 關(guān)鍵K钔!
checkForComodification();
int i = cursor;
if (i >= elementCount)
throw new NoSuchElementException();
cursor = i + 1;
return elementData(lastRet = i);
}
}
final void checkForComodification() {
// 真像在此理疙,無話可說了吧
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
總結(jié)
- Vector內(nèi)部使用數(shù)組來存儲數(shù)據(jù)晕城,當(dāng)數(shù)組內(nèi)存不足以存儲數(shù)據(jù)時,會進行擴容窖贤,默認(rèn)在原來容器大小的基礎(chǔ)上+10砖顷,以達到擴容的目的,這也就是我們常說的動態(tài)擴容主之,但我們使用的過程中進行去指定初始大小择吊,避免擴容,因為擴容的效率和數(shù)據(jù)量成反比槽奕。
- Vector大部分的方法都使用synchronized進行修飾几睛,保證了線程安全,但在并發(fā)場景下效率極低粤攒,這也是我們在工作中基本不使用的原因所森。
- modCount屬性是父類AbstractList的一個屬性,該屬性是為了防止在使用迭代器Iterator遍歷容器的過程中對容器進行修改夯接,導(dǎo)致遍歷數(shù)據(jù)不正確焕济。
到這里我們對Vector分析也完成了,Vector的源碼是真的很簡單盔几,適合初學(xué)者看晴弃,不要害怕,干就是了逊拍,如有寫得不對的地方煩請指出上鞠,一起學(xué)習(xí)進步。
下篇文章分析ArrayList芯丧,其實看明白了Vector芍阎,ArrayList也就懂了。
我是一個愛看源碼的老謝缨恒,知道越多谴咸,不知道的越多轮听。