Java 集合類提供了一套設計良好的支持對一組對象進行操做的接口和類,JAVA經常使用的集合接口有4類,分別是:java
JAVA集合的類關係能夠用圖表示以下:編程
類圖說明:經過類圖咱們知道,全部的集合都繼承了Iterator接口,也就是說,全部的集合都具備迭代器,能夠經過迭代器去循環,事實上,不少集合的功能都是依託於迭代器去實現的。數組
方法名 | 功能 |
---|---|
size() | 返回當前集合的元素個數 |
isEmpty() | 判斷當前集合是不是空元素 |
contains(Object o) | 判斷當前集合是否包含某個對象 |
indexOf(Object o) | 獲取某個對象位於集合的索引位置 |
lastIndexOf(Object o) | 獲取最後一個位於集合的索引位置 |
get(int index) | 獲取指定位置的集合對象 |
set(int index, E element) | 覆蓋集合某個位置的對象 |
add(E e) | 添加對象進入集合 |
add(int index, E element) | 添加對象進入集合指定位置 |
remove(int index) | 移除索引位置的元素 |
remove(Object o) | 移除某個元素 |
咱們通常使用ArrayList最經常使用的方法無非就是添加,查詢和刪除。咱們接下來從源碼層面上分析下ArrayList是如何進行添加,查詢和刪除的。bash
ArrayList源碼屬性工具
//默認容量長度
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;
複製代碼
ArrayList構造方法post
//指定容量構造方法
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//默認無參數構造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//指定集合構造方法
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//官方的一個bug,c.toArray()可能不是一個object數組,因此須要經過Arrays.copyOf建立1個Object[]數組,這樣數組中就能夠存聽任意對象了
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
複製代碼
經過上面ArrayList的構造方法咱們知道,ArrayList能夠建立指定長度的list,也能夠指定一個集合建立list,而默認的建立list是一個長度爲10 的空數組。ui
ArrayList的add()方法this
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
// 確認可否裝得下size+1的對象
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//計算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//若是是默認長度,就比較默認長度和size+1,取最大值
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) {
//取數組的長度
int oldCapacity = elementData.length;
//計算新長度,新長度=舊長度+舊長度/2
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);
}
複製代碼
上面源碼邏輯包括了,ArrayList的添加以及擴容,根據上面源碼,咱們知道,原來ArrayList的實際默認容量直到調用add()方法纔會真正擴容到10,這裏經過new ArrayList()在內存分配的是一個空數組,並無直接new Object[10],這樣設計是很巧妙的,能夠節省不少空間。spa
ArrayList的add(int index, E element)方法設計
public void add(int index, E element) {
//判斷是否越界
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
// 從新複製數組,把index+1位置日後的對象所有後移
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//覆蓋index位置的對象
elementData[index] = element;
size++;
}
複製代碼
ArrayList的指定位置添加對象方法,須要把指定位置後面的所有對象後移,因此這樣也是ArrayList相對於linkList添加耗時的地方。
ArrayList的get(int index)方法
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
複製代碼
ArrayList的get(int index) 方法比較簡單,只有兩步,第一,檢查是否越界,第二,返回數組索引位置的數據。
ArrayList的remove(int index)方法
public E remove(int index) {
rangeCheck(index);
//父類的屬性,用來記錄list修改的次數,後續迭代器中會用到
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
//把index位置後面的元素左移
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
複製代碼
ArrayList 的remove(int index)方法主要分爲 3步,第一步,判斷下標是否越界,第二步,記錄修改次數,並左移index位置後面的元素,第三,把最後位置賦值爲null,用於快速垃圾回收。
ArrayList在循環中使用remove方法須要注意的問題
List<Integer> integers = new ArrayList<>(5);
integers.add(1);
integers.add(2);
integers.add(3);
integers.add(4);
integers.add(5);
for (int i = 0; i < integers.size(); i++) {
integers.remove(i);
}
System.out.println(integers.size());
複製代碼
這裏首先申明一個長度爲5的ArrayList的集合,而後添加五個元素,最後經過循環遍歷刪除,理論結果輸出0,可是輸出的結果倒是2,爲何呢?以前分析remove源碼咱們知道,ArrayList每刪除一次就會把後面的所有元素左移,以這5個元素爲例,第一個正常刪除沒問題,刪除後,元素就只剩下[2,3,4,5],這個時候remove(1),還剩[2,4,5],再remove(2),剩下[2,4],後面再remove沒有元素了,因此最後size爲2。
List<Integer> integers = new ArrayList<>(5);
integers.add(1);
integers.add(2);
integers.add(3);
integers.add(4);
integers.add(5);
for (Integer integer : integers) {
integers.remove(integer);
}
System.out.println(integers.size());
複製代碼
這段代碼只是在上面的代碼上面把for循環改爲了foreach循環,這裏理論結果也是輸出0,可是最後卻報錯了,報錯信息:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
複製代碼
這裏咱們發現是ArrayList的迭代器方法,ArrayList$Itr說明是ArrayList的內部類Itr中checkForComodification出問題了,我查看下源碼,
//這是Itr內部的屬性,初始化等於ArrayList中的modCount
int expectedModCount = modCount;
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
複製代碼
看到這裏咱們應該清楚了,咱們調用ArrayList的remove方法,modCount的值修改了,可是迭代器中expectedModCount值沒有修改,因此就拋出異常了。這時候確定有人說,你這個是騙人的,我寫的foreach刪除就不會報錯!恩,對!有一種狀況是不會報錯的,就是list中只有兩個元素時,好比這樣:
List<Integer> integers = new ArrayList<>(5);
integers.add(1);
integers.add(2);
for (Integer integer : integers) {
integers.remove(integer);
}
System.out.println(integers.size());
}
複製代碼
這時候輸出結果爲1,沒有報錯,爲何呢?咱們知道foreach是for循環的加強,內部是經過迭代器實現的,看到剛剛報錯的代碼也證明了咱們的猜測,因此,迭代器刪除,過程是這樣的,先判斷iterator.hasNext(),迭代器有沒有下一個元素,若是有就遍歷,遍歷就會調用iterator.next(),該源碼以下:
public boolean hasNext() {
return cursor != size;
}
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
複製代碼
咱們查看源碼發現,以上過程只有調用next()會進行 checkForComodification()
,當咱們刪除了第一個元素時候,進入循環判斷,hasNext這個時候爲false,不會調用next(),因此也就不會執行checkForComodification()
,因此就能輸出1。