Java 集合學習--ArrayList
一、ArrayList 定義
ArrayList 是一個用數組實現的集合,支持隨機訪問,元素有序且可以重復。
①、實現 List 接口
List接口繼承Collection接口,是List類的頂層接口,定義了大量方法,子類可進行個性化實現
②、實現RandomAccess接口
RandomAccess 接口是一個標記接口,類似我們熟悉的Serializable接口,表明支持隨機訪問,在工具類Collections中有發揮其作用。
③、實現 Cloneable 接口
能否調用Object.clone() 方法的關鍵,如果未實現,調用clone則拋出CloneNoSupportException異常。
④、實現 Serializable 接口
這個接口沒什麽好說的,能都進行序列化的關鍵。
二、字段屬性
ArrayList類的主要屬性如下:
//Arraylist默認初始大小 private static final int DEFAULT_CAPACITY = 10; //空的數組實例 private static final Object[] EMPTY_ELEMENTDATA = {}; //這也是一個空的數組實例,與上面空數組的區別在於,當第一個元素添加的時候,需要膨脹多少 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//存儲 ArrayList集合的元素的數組 transient Object[] elementData; //ArrayList集合元素個數 private int size;
三、構造函數
1.無參構造函數,構造一個空的數組。
2.有參構造函數,參數指定數組的初始大小。構造給定的初始大小的數組。
3.有參構造函數,參數是一個集合,最終是將給定的集合復制到數組中。
四、主要方法
1.添加元素
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e;return true; }
在添加元素之前,需要調用ensureCapacityInternal()方法來確定數組的大小,保證數組有足夠的大小來容量新增的元素。如果數組已經滿了,則進行數組增大,主要是借助Arrays.copyOf()方法實現的。
1 private void ensureExplicitCapacity(int minCapacity) { 2 modCount++; 3 4 // overflow-conscious code 5 if (minCapacity - elementData.length > 0) 6 grow(minCapacity); 7 } 8 9 private void grow(int minCapacity) { 10 // overflow-conscious code 11 int oldCapacity = elementData.length; 12 int newCapacity = oldCapacity + (oldCapacity >> 1); 13 if (newCapacity - minCapacity < 0) 14 newCapacity = minCapacity; 15 if (newCapacity - MAX_ARRAY_SIZE > 0) 16 newCapacity = hugeCapacity(minCapacity); 17 // minCapacity is usually close to size, so this is a win: 18 elementData = Arrays.copyOf(elementData, newCapacity); 19 }
①、當通過 ArrayList() 構造一個空集合,初始長度是為0的,第 1 次添加元素,會創建一個長度為10的數組,並將該元素賦值到數組的第一個位置。
②、第 2 次添加元素,集合不為空,而且由於集合的長度size+1是小於數組的長度10,所以直接添加元素到數組的第二個位置,不用擴容。
③、第 11 次添加元素,此時 size+1 = 11,而數組長度是10,這時候創建一個長度為10+10*0.5 = 15 的數組(擴容1.5倍),然後將原數組元素引用拷貝到新數組。並將第 11 次添加的元素賦值到新數組下標為10的位置。
數組的最大長度為 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
2.刪除元素
①、通過數組索引刪除元素
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }
首先判斷索引的值是否大於等於size,超過集合的大小則拋出IndexOutOfBoundsException異常,再通過System.arraycopy()方法對數組進行復制操作,最終形成的數組是刪除指定位置後的數組,達到了移動數組中元素位置的效果。
②、直接刪除指定元素
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; }
刪除指定元素的邏輯非常簡單,即首先遍歷數組,找到指定元素在數組中的位置後,最終的邏輯和通過索引刪除元素一致。
3.查找元素
public E get(int index) { rangeCheck(index); return elementData(index); }
查找元素是根據索引查找,即通過索引獲取數組中的值。
4.集合遍歷
①、普通 for 循環遍歷,通過循環,在借助get方法即可遍歷ArrayList集合。
②、通過terator遍歷
首先獲取叠代器,其返回的是ArrayList類中自定義的內部類Itr。
public Iterator<E> iterator() { return new Itr(); }
1 private class Itr implements Iterator<E> { 2 int cursor; // index of next element to return 3 int lastRet = -1; // index of last element returned; -1 if no such 4 int expectedModCount = modCount; 5 6 Itr() {} 7 8 public boolean hasNext() { 9 return cursor != size; 10 } 11 12 @SuppressWarnings("unchecked") 13 public E next() { 14 checkForComodification(); 15 int i = cursor; 16 if (i >= size) 17 throw new NoSuchElementException(); 18 Object[] elementData = ArrayList.this.elementData; 19 if (i >= elementData.length) 20 throw new ConcurrentModificationException(); 21 cursor = i + 1; 22 return (E) elementData[lastRet = i]; 23 } 24 25 public void remove() { 26 if (lastRet < 0) 27 throw new IllegalStateException(); 28 checkForComodification(); 29 30 try { 31 ArrayList.this.remove(lastRet); 32 cursor = lastRet; 33 lastRet = -1; 34 expectedModCount = modCount; 35 } catch (IndexOutOfBoundsException ex) { 36 throw new ConcurrentModificationException(); 37 } 38 } 39 40 @Override 41 @SuppressWarnings("unchecked") 42 public void forEachRemaining(Consumer<? super E> consumer) { 43 Objects.requireNonNull(consumer); 44 final int size = ArrayList.this.size; 45 int i = cursor; 46 if (i >= size) { 47 return; 48 } 49 final Object[] elementData = ArrayList.this.elementData; 50 if (i >= elementData.length) { 51 throw new ConcurrentModificationException(); 52 } 53 while (i != size && modCount == expectedModCount) { 54 consumer.accept((E) elementData[i++]); 55 } 56 // update once at end of iteration to reduce heap write traffic 57 cursor = i; 58 lastRet = i - 1; 59 checkForComodification(); 60 } 61 62 final void checkForComodification() { 63 if (modCount != expectedModCount) 64 throw new ConcurrentModificationException(); 65 } 66 }View Code
③、forEach遍歷ArrayList本質即通過叠代器遍歷。
5.toArray,ArrayList集合轉換成數組
public Object[] toArray() { return Arrays.copyOf(elementData, size); }
6.trimToSize()
public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
這個方法主要是釋放內存,將ArrayList集合中數組的大小調整為size的大小。
7.subList方法
這個方法往往引起我們犯錯,subList返回的並不是一個ArrayList。經常我們容易使用subList後,再使用add方法,然後報異常。具體原因源碼顯示的很清楚。
public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex); }
其他方法的學習省略,感興趣的可以參考源碼。
五、總結
- ArrayList是基於數組實現的,是一個動態數組,其容量能自動增長,類似於C語言中的動態申請內存,動態增長內存。
- ArrayList不是線程安全的,只能用在單線程環境下,多線程環境下可以考慮用Collections.synchronizedList(List l)函數返回一個線程安全的ArrayList類,也可以使用concurrent並發包下的CopyOnWriteArrayList類。
- ArrayList的實現中大量地調用了Arrays.copyof()和System.arraycopy()方法
- ArrayList基於數組實現,可以通過下標索引直接查找到指定位置的元素,因此查找效率高,但每次插入或刪除元素,就要大量地移動元素,插入刪除元素的效率低。
- ArrayList中允許元素為null,且可以重復。
Java 集合學習--ArrayList