JAVA集合-Lsit集合分析-ArrayList
ArrayList 特點:
1.ArrayList 繼承了AbstractList,所以是非執行緒安全。
2.ArrayList 實現了RandomAccess介面,所以可以隨機訪問(即通過下標進行訪問,獲取元素)
3.ArrayList 實現了Cloneable介面,即覆蓋了函式clone(),能被克隆。
4.ArrayList 實現java.io.Serializable介面,這意味著ArrayList支援序列化,能通過序列化去傳輸。
1.1 類中的屬性
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; // 版本號 private static final int DEFAULT_CAPACITY = 10; // 預設容量 private static final Object[] EMPTY_ELEMENTDATA = {}; // 空物件陣列 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 預設空物件陣列 transient Object[] elementData;// 元素陣列 private int size; // 實際元素大小,預設為0 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 最大陣列容量 }
1.2構造方法
1.ArrayList() 2.ArrayList(int) 3.ArrayList(Collection<? extends E> c)
1.無參構造方法
/** * Constructs an empty list with an initial capacity of ten. 這裡就說明了預設會給10的大小,所以說一開始arrayList的容量是10. */ //ArrayList中儲存資料的其實就是一個數組,這個陣列就是elementData,在123行定義的 private transient Object[] elementData; public ArrayList() { super(); //呼叫父類中的無參構造方法,父類中的是個空的構造方法 this.elementData = EMPTY_ELEMENTDATA;//EMPTY_ELEMENTDATA:是個空的Object[], 將elementData初始化,elementData也是個Object[]型別。空的Object[]會給預設大小10,等會會解釋什麼時候賦值的。 }
2.有參構造方法一
public ArrayList(int initialCapacity) { super(); //父類中空的構造方法 if (initialCapacity < 0) //判斷如果自定義大小的容量小於0,則報下面這個非法資料異常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; //將自定義的容量大小當成初始化elementData的大小 }
3.有參構造方法二
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray(); //轉換為陣列
size = elementData.length; //陣列中的資料個數
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class) //每個集合的toarray()的實現方法不一樣,所以需要判斷一下,如果不是Object[].class型別,那麼久需要使用ArrayList中的方法去改造一下。
elementData = Arrays.copyOf(elementData, size, Object[].class);//複製資料到elementData中
}
1.1總結:3個構造方法都是為了初始化elementData陣列
2.常用方法
2.1 四種add()方法
①boolean add(E) ; ②void add(int,E) ③ boolean add(int,Collection<? extends E> ) ④boolean addAll(Collection<? extends E>)
2.1.1 boolean add(E);//預設直接在末尾新增元素
public boolean add(E e) {
//確定內部容量是否夠了,size是陣列中資料的個數,因為要新增一個元素,所以size+1,先判斷size+1的這個個數陣列能否放得下,就在這個方法中去判斷是否陣列.length是否夠用了。
ensureCapacityInternal(size + 1);
//在資料中正確的位置上放上元素e,並且size++
elementData[size++] = e;
return true;
}
ensureCapacityInternal(xxx); 確定內部容量的方法
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) { //看,判斷初始化的elementData是不是空的陣列,也就是沒有長度
//因為如果是空的話,minCapacity=size+1;其實就是等於1,空的陣列沒有長度就存放不了,所以就將minCapacity變成10,也就是預設大小,但是帶這裡,還沒有真正的初始化這個elementData的大小。
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//確認實際的容量,上面只是將minCapacity=10,這個方法就是真正的判斷elementData是否夠用
ensureExplicitCapacity(minCapacity);
}
ensureExplicitCapacity(xxx);
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//修改次數加一
//minCapacity如果大於了實際elementData的長度,那麼就說明elementData陣列的長度不夠用,不夠用那麼就要增加elementData的length。這裡有的同學就會模糊minCapacity到底是什麼呢,這裡給你們分析一下
/*第一種情況:由於elementData初始化時是空的陣列,那麼第一次add的時候,minCapacity=size+1;也就minCapacity=1,在上一個方法(確定內部容量ensureCapacityInternal)就會判斷出是空的陣列,就會給
將minCapacity=10,到這一步為止,還沒有改變elementData的大小。
第二種情況:elementData不是空的陣列了,那麼在add的時候,minCapacity=size+1;也就是minCapacity代表著elementData中增加之後的實際資料個數,拿著它判斷elementData的length是否夠用,如果length
不夠用,那麼肯定要擴大容量,不然增加的這個元素就會溢位。
*/
if (minCapacity - elementData.length > 0)//需要的長度>陣列的長度
//arrayList能自動擴充套件大小的關鍵方法就在這裡了
grow(minCapacity);
}
grow(xxx); arrayList核心的方法,能擴充套件陣列大小。
private void grow(int minCapacity) {
int oldCapacity = elementData.length; //將擴充前的elementData大小給oldCapacity
int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity
if (newCapacity - minCapacity < 0)//這句話就是適應於elementData就空陣列的時候,length=0,那麼oldCapacity=0,newCapacity=0,所以這個判斷成立,在這裡就是真正的初始化elementData的大小了,就是為10.前面的工作都是準備工作。
newCapacity = minCapacity;.
if (newCapacity - MAX_ARRAY_SIZE > 0)//如果newCapacity超過了最大的容量限制,就呼叫hugeCapacity,也就是將能給的最大值給newCapacity
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//新的容量大小已經確定好了,就copy陣列,改變容量大小咯。
elementData = Arrays.copyOf(elementData, newCapacity);
}
hugeCapacity() 將陣列擴充為最大值
//這個就是上面用到的方法,很簡單,就是用來賦最大值。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//如果minCapacity都大於MAX_ARRAY_SIZE,那麼就Integer.MAX_VALUE返回,反之將MAX_ARRAY_SIZE返回。因為maxCapacity是三倍的minCapacity,可能擴充的太大了,就用minCapacity來判斷了。
//Integer.MAX_VALUE:2147483647 MAX_ARRAY_SIZE:2147483639 也就是說最大也就能給到第一個數值。還是超過了這個限制,就要溢位了。相當於arraylist給了兩層防護。
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
2.1.2 boolean add(E);//預設直接在末尾新增元素
public void add(int index, E element) {
rangeCheckForAdd(index);//檢查index也就是插入的位置是否合理。
//跟上面的分析一樣,具體看上面
ensureCapacityInternal(size + 1); // Increments modCount!!
//這個方法就是用來在插入元素之後,要將index之後的元素都往後移一位,
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//在目標位置上存放元素
elementData[index] = element;
size++;//size增加1
}
rangeCheckForAdd(index)
private void rangeCheckForAdd(int index) {
if (index > size || index < 0) //插入的位置肯定不能大於size 和小於0
//如果是,就報這個越界異常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
System.arraycopy(...):就是將elementData在插入位置後的所有元素往後面移一位。
public static void arraycopy(Object src,
int srcPos,
Object dest,
int destPos,
int length)
src:源物件
srcPos:源物件物件的起始位置
dest:目標物件
destPost:目標物件的起始位置
length:從起始位置往後複製的長度。
add()方法流程總結:
2.2刪除方法
2.1.1 remove(int):通過刪除指定位置上的元素
public E remove(int index) {
rangeCheck(index);//檢查index的合理性
modCount++;//這個作用很多,比如用來檢測快速失敗的一種標誌。
E oldValue = elementData(index);//通過索引直接找到該元素
int numMoved = size - index - 1;//計算要移動的位數。
if (numMoved > 0)
//這個方法也已經解釋過了,就是用來移動元素的。
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//將--size上的位置賦值為null,讓gc(垃圾回收機制)更快的回收它。
elementData[--size] = null; // clear to let GC do its work
//返回刪除的元素。
return oldValue;
}
2.1.2 remove(Object):這個方法可以看出來,arrayList是可以存放null值得。
//感覺這個不怎麼要分析吧,都看得懂,就是通過元素來刪除該元素,就依次遍歷,如果有這個元素,就將該元素的索引傳給fastRemobe(index),使用這個方法來刪除該元素,
//fastRemove(index)方法的內部跟remove(index)的實現幾乎一樣,這裡最主要是知道arrayList可以儲存null值
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
2.1.3 clear():將elementData中每個元素都賦值為null,等待垃圾回收將這個給回收掉,所以叫clear
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
2.1.4 removeAll(collection c)
public boolean removeAll(Collection<?> c) {
return batchRemove(c, false);//批量刪除
}
batchRemove(xx,xx):用於兩個方法,一個removeAll():它只清楚指定集合中的元素,retainAll()用來測試兩個集合是否有交集。
//這個方法,用於兩處地方,如果complement為false,則用於removeAll如果為true,則給retainAll()用,retainAll()是用來檢測兩個集合是否有交集的。
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData; //將原集合,記名為A
int r = 0, w = 0; //r用來控制迴圈,w是記錄有多少個交集
boolean modified = false;
try {
for (; r < size; r++)
//引數中的集合C一次檢測集合A中的元素是否有,
if (c.contains(elementData[r]) == complement)
//有的話,就給集合A
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
//如果contains方法使用過程報異常
if (r != size) {
//將剩下的元素都賦值給集合A,
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
//這裡有兩個用途,在removeAll()時,w一直為0,就直接跟clear一樣,全是為null。
//retainAll():沒有一個交集返回true,有交集但不全交也返回true,而兩個集合相等的時候,返回false,所以不能根據返回值來確認兩個集合是否有交集,而是通過原集合的大小是否發生改變來判斷,如果原集合中還有元素,則代表有交集,而元集合沒有元素了,說明兩個集合沒有交集。
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
總結::remove函式使用者移除指定下標的元素,此時會把指定下標到陣列末尾的元素向前移動一個單位,並且會把陣列最後一個元素設定為null
2.3 set()方法
public E set(int index, E element) {
// 檢驗索引是否合法
rangeCheck(index);
// 舊值
E oldValue = elementData(index);
// 賦新值
elementData[index] = element;
// 返回舊值
return oldValue;
}
2.4 indexOf()方法
// 從首開始查詢數組裡面是否存在指定元素
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;
}
2.5 get(int index)
public E get(int index) {
// 檢驗索引是否合法
rangeCheck(index);
return elementData(index);
}
說明:get函式會檢查索引值是否合法(只檢查是否大於size,而沒有檢查是否小於0),值得注意的是,在get函式中存在element函式,element函式用於返回具體的元素,具體函式如下:
E elementData(int index) {
return (E) elementData[index];
}
3.總結
1.ArrayList 可以存放null
2.ArrayList本質是一個叫elementData陣列,所以查詢會很快,而刪除,增加會很慢
3.ArrayList 能夠自動擴容 關鍵在於gorw()方法。