文章目錄
要點速記: 擴容=1.5倍+1,數組和鏈表的區別
1. 類結構
public class ArrayList extends AbstractList implements List , RandomAccess, Cloneable, java.io.Serializable
1.1、 實現接口
- RandmoAccess接口:
是java中用來被List實現,爲List提供快速訪問功能的。在ArrayList中,我們即可以通過元素的序號快速獲取元素對象;這就是快速隨機訪問
想詳細瞭解的呢可以看RandomAccess接口的使用
- Cloneable接口:
即覆蓋了函數clone(),能被克隆 - Serializable接口
意味着ArrayList支持序列化,能通過序列化去傳輸 - List接口:
在Java集合分類中,List代表有序,可重複
具體和下面父類一起說 - 父類AbstractList
ArrayList 繼承了AbstractList,父類也實現了List。它是一個數組隊列,提供了相關的添加、刪除、修改、遍歷等功能
1.2、 它跟數組的關係?
1.2.1、數組VS鏈表
Array(數組),數據結構適合內存結構也是連續的(適合遍歷),隨機訪問(適合查找),不適合增刪(鏈表則相反),同時在創建數組的同時你必須指定大小
想知道詳細的可以看看數組與鏈表的優缺點
1.3.2、 數組 VS ArrayList
- Array類型的變量在聲明的同時必須進行實例化(至少得初始化數組的大小),而ArrayList可以只是先聲明。
- Array只能存儲同構的對象,而ArrayList可以存儲異構的對象。
List內部就是使用"object[] _items;"這樣一個私有字段來封裝對象的) - 在CLR託管對中的存放方式:
Array是始終是連續存放的,而ArrayList的存放不一定連續。 - 初始化大小:
Array對象的初始化必須只定指定大小,且創建後的數組大小是固定的,
而ArrayList的大小可以動態指定,其大小可以在初始化時指定,也可以不指定,也就是說該對象的空間可以任意增加。 - Array不能夠隨意添加和刪除其中的項,而ArrayList可以在任意位置插入和刪除項。
同構和異構:
同構的對象是指類型相同的對象,若聲明爲int[]的數組就只能存放整形數據,string[]只能存放字符型數據,但聲明爲object[]的數組除外。
而ArrayList可以存放任何不同類型的數據(因爲它裏面存放的都是被裝箱了的Object型對象,實際上Array
回到正文了,去看看ArrayList源碼羅
2、探索源碼
2.1、 構造
// ArrayList帶容量大小的構造函數。
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
// 新建一個數組
this.elementData = new Object[initialCapacity];
}
// ArrayList構造函數。默認容量是10。
public ArrayList() {
this(10);
}
// 構造一個包含指定元素的list,這些元素的是按照Collection的迭代器返回的順序排列的
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
其中構造方法牽扯到了兩個屬性
// 保存ArrayList中數據的數組
private transient Object[] elementData;
// ArrayList中實際數據的數量
private int size;
(1) elementData :Object[]類型的數組,它保存了添加到ArrayList中的元素。
實際上,elementData是個動態數組,我們能通過構造函數 ArrayList(int initialCapacity)來執行它的初始容量爲initialCapacity;
如果通過不含參數的構造函數ArrayList()來創建ArrayList,則elementData的容量默認是10。elementData數組的大小會根據ArrayList容量的增長而動態的增長
具體的增長方式,請參考源碼分析中的ensureCapacity()函數。
(2) size 則是動態數組的實際大小。
2.2、添加
/**
* 添加一個元素
*/
public boolean add(E e) {
// 進行擴容檢查
ensureCapacity( size + 1); // Increments modCount
// 將e增加至list的數據尾部,容量+1
elementData[size ++] = e;
return true;
}
/**
* 在指定位置添加一個元素
*/
public void add(int index, E element) {
// 判斷索引是否越界,這裏會拋出多麼熟悉的異常。。。
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: " +size);
// 進行擴容檢查
ensureCapacity( size+1); // Increments modCount
// 對數組進行復制處理,目的就是空出index的位置插入element,並將index後的元素位移一個位置
System. arraycopy(elementData, index, elementData, index + 1,
size - index);
// 將指定的index位置賦值爲element
elementData[index] = element;
// list容量+1
size++;
}
/**
* 增加一個集合元素
*/
public boolean addAll(Collection<? extends E> c) {
//將c轉換爲數組
Object[] a = c.toArray();
int numNew = a.length ;
//擴容檢查
ensureCapacity( size + numNew); // Increments modCount
//將c添加至list的數據尾部
System. arraycopy(a, 0, elementData, size, numNew);
//更新當前容器大小
size += numNew;
return numNew != 0;
}
/**
* 在指定位置,增加一個集合元素
*/
public boolean addAll(int index, Collection<? extends E> c) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: " + index + ", Size: " + size);
Object[] a = c.toArray();
int numNew = a.length ;
ensureCapacity( size + numNew); // Increments modCount
// 計算需要移動的長度(index之後的元素個數)
int numMoved = size - index;
// 數組複製,空出第index到index+numNum的位置,即將數組index後的元素向右移動numNum個位置
if (numMoved > 0)
System. arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 將要插入的集合元素複製到數組空出的位置中
System. arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
/**
* 數組容量檢查,不夠時則進行擴容
*/
public void ensureCapacity( int minCapacity) {
modCount++;
// 當前數組的長度
int oldCapacity = elementData .length;
// 最小需要的容量大於當前數組的長度則進行擴容
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
// 新擴容的數組長度爲舊容量的1.5倍+1
int newCapacity = (oldCapacity * 3)/2 + 1;
// 如果新擴容的數組長度還是比最小需要的容量小,則以最小需要的容量爲長度進行擴容
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
// 進行數據拷貝,Arrays.copyOf底層實現是System.arrayCopy()
elementData = Arrays.copyOf( elementData, newCapacity);
}
}
2.3、 刪除
/**
* 根據索引位置刪除元素
*/
public E remove( int index) {
// 數組越界檢查
RangeCheck(index);
modCount++;
// 取出要刪除位置的元素,供返回使用
E oldValue = (E) elementData[index];
// 計算數組要複製的數量
int numMoved = size - index - 1;
// 數組複製,就是將index之後的元素往前移動一個位置
if (numMoved > 0)
System. arraycopy(elementData, index+1, elementData, index,
numMoved);
// 將數組最後一個元素置空(因爲刪除了一個元素,然後index後面的元素都向前移動了,所以最後一個就沒用了),好讓gc儘快回收
// 不要忘了size減一
elementData[--size ] = null; // Let gc do its work
return oldValue;
}
/**
* 根據元素內容刪除,只刪除匹配的第一個
*/
public boolean remove(Object o) {
// 對要刪除的元素進行null判斷
// 對數據元素進行遍歷查找,知道找到第一個要刪除的元素,刪除後進行返回,如果要刪除的元素正好是最後一個那就慘了,時間複雜度可達O(n) 。。。
if (o == null) {
for (int index = 0; index < size; index++)
// null值要用==比較
if (elementData [index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
// 非null當然是用equals比較了
if (o.equals(elementData [index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
// 原理和之前的add一樣,還是進行數組複製,將index後的元素向前移動一個位置,不細解釋了,
int numMoved = size - index - 1;
if (numMoved > 0)
System. arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size ] = null; // Let gc do its work
}
/**
* 數組越界檢查
*/
private void RangeCheck(int index) {
if (index >= size )
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: " +size);
}
2.4 更新
/**
* 將指定位置的元素更新爲新元素
*/
public E set( int index, E element) {
// 數組越界檢查
RangeCheck(index);
// 取出要更新位置的元素,供返回使用
E oldValue = (E) elementData[index];
// 將該位置賦值爲行的元素
elementData[index] = element;
// 返回舊元素
return oldValue;
}
2.5查找
/**
* 查找指定位置上的元素
*/
public E get( int index) {
RangeCheck(index);
return (E) elementData [index];
}
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData [i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData [i]))
return i;
}
return -1;
}
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData [i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData [i]))
return i;
}
return -1;
}
contains主要是檢查indexOf,也就是元素在list中出現的索引位置也就是數組下標,再看indexOf和lastIndexOf代碼是不是很熟悉
沒錯,和public boolean remove(Object o) 的代碼一樣,都是元素null判斷,都是循環比較,不多說了。。。但是要知道,最差的情況(要找的元素是最後一個)也是很慘的。。。
3. ArrayList3種遍歷方式
(01) 第一種,通過迭代器遍歷。即通過Iterator去遍歷。
Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
value = (Integer)iter.next();
}
(02) 第二種,隨機訪問,通過索引值去遍歷。
由於ArrayList實現了RandomAccess接口,它支持通過索引值去隨機訪問元素。
Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
value = (Integer)list.get(i);
}
(03) 第三種,for循環遍歷。如下:
Integer value = null;
for (Integer integ:list) {
value = integ;
}
總結迭代速度是最慢的,隨機訪問是最快得
4 總結
ArrayList和LinkedList的區別
- ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。
- 對於隨機訪問get和set,ArrayList覺得優於LinkedList,因爲LinkedList要移動指針。
- 對於新增和刪除操作add和remove,LinkedList比較佔優勢,因爲ArrayList要移動數據。
ArrayList和Vector的區別
- Vector和ArrayList幾乎是完全相同的,唯一的區別在於Vector是同步類(synchronized),屬於強同步類。因此開銷就比ArrayList要大,訪問要慢。正常情況下,大多數的Java程序員使用ArrayList而不是Vector,因爲同步完全可以由程序員自己來控制。
- Vector每次擴容請求其大小的2倍空間,而ArrayList是1.5倍。
- Vector還有一個子類Stack.