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是如何進行添加,查詢和刪除的。post
ArrayList源碼屬性this
//默認容量長度 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構造方法設計
//指定容量構造方法 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 的空數組。code
ArrayList的add()方法對象
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],這樣設計是很巧妙的,能夠節省不少空間。blog
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。