關於 JDK 的集合類的總體介紹能夠看這張圖,本篇博客咱們不繫統的介紹整個集合的構造,重點是介紹 ArrayList 類是如何實現的。html
ArrayList 是一個用數組實現的集合,支持隨機訪問,元素有序且能夠重複。java
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
①、實現 RandomAccess 接口算法
這是一個標記接口,通常此標記接口用於 List 實現,以代表它們支持快速(一般是恆定時間)的隨機訪問。該接口的主要目的是容許通用算法改變其行爲,以便在應用於隨機或順序訪問列表時提供良好的性能。api
好比在工具類 Collections(這個工具類後面會詳細講解)中,應用二分查找方法時判斷是否實現了 RandomAccess 接口:數組
1 int binarySearch(List<? extends Comparable<? super T>> list, T key) { 2 if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD) 3 return Collections.indexedBinarySearch(list, key); 4 else 5 return Collections.iteratorBinarySearch(list, key); 6 }
②、實現 Cloneable 接口併發
這個類是 java.lang.Cloneable,前面咱們講解深拷貝和淺拷貝的原理時,咱們介紹了淺拷貝能夠經過調用 Object.clone() 方法來實現,可是調用該方法的對象必需要實現 Cloneable 接口,不然會拋出 CloneNoSupportException異常。
oracle
Cloneable 和 RandomAccess 接口同樣也是一個標記接口,接口內無任何方法體和常量的聲明,也就是說若是想克隆對象,必需要實現 Cloneable 接口,代表該類是能夠被克隆的。app
③、實現 Serializable 接口dom
這個沒什麼好說的,也是標記接口,表示能被序列化。ide
④、實現 List 接口
這個接口是 List 類集合的上層接口,定義了實現該接口的類都必需要實現的一組方法,以下所示,下面咱們會對這一系列方法的實現作詳細介紹。
//集合的默認大小 private static final int DEFAULT_CAPACITY = 10; //空的數組實例 private static final Object[] EMPTY_ELEMENTDATA = {}; //這也是一個空的數組實例,和EMPTY_ELEMENTDATA空數組相比是用於瞭解添加元素時數組膨脹多少 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //存儲 ArrayList集合的元素,集合的長度即這個數組的長度 //一、當 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 時將會清空 ArrayList //二、當添加第一個元素時,elementData 長度會擴展爲 DEFAULT_CAPACITY=10 transient Object[] elementData; //表示集合的長度 private int size;
1 public ArrayList() { 2 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 3 }
此無參構造函數將建立一個 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 聲明的數組,注意此時初始容量是0,而不是你們覺得的 10。
注意:根據默認構造函數建立的集合,ArrayList list = new ArrayList();此時集合長度是0.
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); } }
初始化集合大小建立 ArrayList 集合。當大於0時,給定多少那就建立多大的數組;當等於0時,建立一個空數組;當小於0時,拋出異常。
1 public ArrayList(Collection<? extends E> c) { 2 elementData = c.toArray(); 3 if ((size = elementData.length) != 0) { 4 // c.toArray might (incorrectly) not return Object[] (see 6260652) 5 if (elementData.getClass() != Object[].class) 6 elementData = Arrays.copyOf(elementData, size, Object[].class); 7 } else { 8 // replace with empty array. 9 this.elementData = EMPTY_ELEMENTDATA; 10 } 11 }
這是將已有的集合複製到 ArrayList 集合中去。
經過前面的字段屬性和構造函數,咱們知道 ArrayList 集合是由數組構成的,那麼向 ArrayList 中添加元素,也就是向數組賦值。咱們知道一個數組的聲明是能肯定大小的,而使用 ArrayList 時,好像是能添加任意多個元素,這就涉及到數組的擴容。
擴容的核心方法就是調用前面咱們講過的Arrays.copyOf 方法,建立一個更大的數組,而後將原數組元素拷貝過去便可。下面咱們看看具體實現:
public boolean add(E e) { ensureCapacityInternal(size + 1); //添加元素以前,首先要肯定集合的大小 elementData[size++] = e; return true; }
如上所示,在經過調用 add 方法添加元素以前,咱們要首先調用 ensureCapacityInternal 方法來肯定集合的大小,若是集合滿了,則要進行擴容操做。
1 private void ensureCapacityInternal(int minCapacity) {//這裏的minCapacity 是集合當前大小+1 2 //elementData 是實際用來存儲元素的數組,注意數組的大小和集合的大小不是相等的,前面的size是指集合大小 3 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); 4 } 5 private static int calculateCapacity(Object[] elementData, int minCapacity) { 6 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//若是數組爲空,則從size+1的值和默認值10中取最大的 7 return Math.max(DEFAULT_CAPACITY, minCapacity); 8 } 9 return minCapacity;//不爲空,則返回size+1 10 } 11 private void ensureExplicitCapacity(int minCapacity) { 12 modCount++; 13 14 // overflow-conscious code 15 if (minCapacity - elementData.length > 0) 16 grow(minCapacity); 17 }
在 ensureExplicitCapacity 方法中,首先對修改次數modCount加一,這裏的modCount給ArrayList的迭代器使用的,在併發操做被修改時,提供快速失敗行爲(保證modCount在迭代期間不變,不然拋出ConcurrentModificationException異常,能夠查看源碼865行),接着判斷minCapacity是否大於當前ArrayList內部數組長度,大於的話調用grow方法對內部數組elementData擴容,grow方法代碼以下:
1 private void grow(int minCapacity) { 2 int oldCapacity = elementData.length;//獲得原始數組的長度 3 int newCapacity = oldCapacity + (oldCapacity >> 1);//新數組的長度等於原數組長度的1.5倍 4 if (newCapacity - minCapacity < 0)//當新數組長度仍然比minCapacity小,則爲保證最小長度,新數組等於minCapacity 5 newCapacity = minCapacity; 6 //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 = 2147483639 7 if (newCapacity - MAX_ARRAY_SIZE > 0)//當獲得的新數組長度比 MAX_ARRAY_SIZE 大時,調用 hugeCapacity 處理大數組 8 newCapacity = hugeCapacity(minCapacity); 9 //調用 Arrays.copyOf 將原數組拷貝到一個大小爲newCapacity的新數組(注意是拷貝引用) 10 elementData = Arrays.copyOf(elementData, newCapacity); 11 } 12 13 private static int hugeCapacity(int minCapacity) { 14 if (minCapacity < 0) // 15 throw new OutOfMemoryError(); 16 return (minCapacity > MAX_ARRAY_SIZE) ? //minCapacity > MAX_ARRAY_SIZE,則新數組大小爲Integer.MAX_VALUE 17 Integer.MAX_VALUE : 18 MAX_ARRAY_SIZE; 19 }
對於 ArrayList 集合添加元素,咱們總結一下:
①、當經過 ArrayList() 構造一個空集合,初始長度是爲0的,第 1 次添加元素,會建立一個長度爲10的數組,並將該元素賦值到數組的第一個位置。
②、第 2 次添加元素,集合不爲空,並且因爲集合的長度size+1是小於數組的長度10,因此直接添加元素到數組的第二個位置,不用擴容。
③、第 11 次添加元素,此時 size+1 = 11,而數組長度是10,這時候建立一個長度爲10+10*0.5 = 15 的數組(擴容1.5倍),而後將原數組元素引用拷貝到新數組。並將第 11 次添加的元素賦值到新數組下標爲10的位置。
④、第 Integer.MAX_VALUE - 8 = 2147483639,而後 2147483639%1.5=1431655759(這個數是要進行擴容) 次添加元素,爲了防止溢出,此時會直接建立一個 1431655759+1 大小的數組,這樣一直,每次添加一個元素,都只擴大一個範圍。
⑤、第 Integer.MAX_VALUE - 7 次添加元素時,建立一個大小爲 Integer.MAX_VALUE 的數組,在進行元素添加。
⑥、第 Integer.MAX_VALUE + 1 次添加元素時,拋出 OutOfMemoryError 異常。
注意:能向集合中添加 null 的,由於數組能夠有 null 值存在。
1 Object[] obj = {null,1}; 2 3 ArrayList list = new ArrayList(); 4 list.add(null); 5 list.add(1); 6 System.out.println(list.size());//2
1 public E remove(int index) { 2 rangeCheck(index);//判斷給定索引的範圍,超過集合大小則拋出異常 3 4 modCount++; 5 E oldValue = elementData(index);//獲得索引處的刪除元素 6 7 int numMoved = size - index - 1; 8 if (numMoved > 0)//size-index-1 > 0 表示 0<= index < (size-1),即索引不是最後一個元素 9 //經過 System.arraycopy()將數組elementData 的下標index+1以後長度爲 numMoved的元素拷貝到從index開始的位置 10 System.arraycopy(elementData, index+1, elementData, index, 11 numMoved); 12 elementData[--size] = null; //將數組最後一個元素置爲 null,便於垃圾回收 13 14 return oldValue; 15 }
remove(int index) 方法表示刪除索引index處的元素,首先經過 rangeCheck(index) 方法判斷給定索引的範圍,超過集合大小則拋出異常;接着經過 System.arraycopy 方法對數組進行自身拷貝。關於這個方法的用法能夠參考這篇博客。
1 public boolean remove(Object o) { 2 if (o == null) {//若是刪除的元素爲null 3 for (int index = 0; index < size; index++) 4 if (elementData[index] == null) { 5 fastRemove(index); 6 return true; 7 } 8 } else {//不爲null,經過equals方法判斷對象是否相等 9 for (int index = 0; index < size; index++) 10 if (o.equals(elementData[index])) { 11 fastRemove(index); 12 return true; 13 } 14 } 15 return false; 16 } 17 18 19 private void fastRemove(int index) { 20 modCount++; 21 int numMoved = size - index - 1; 22 if (numMoved > 0) 23 System.arraycopy(elementData, index+1, elementData, index, 24 numMoved); 25 elementData[--size] = null; // 26 }
remove(Object o)方法是刪除第一次出現的該元素。而後經過System.arraycopy進行數組自身拷貝。
經過調用 set(int index, E element) 方法在指定索引 index 處的元素替換爲 element。並返回原數組的元素。
1 public E set(int index, E element) { 2 rangeCheck(index);//判斷索引合法性 3 4 E oldValue = elementData(index);//得到原數組指定索引的元素 5 elementData[index] = element;//將指定所引處的元素替換爲 element 6 return oldValue;//返回原數組索引元素 7 }
經過調用 rangeCheck(index) 來檢查索引合法性。
private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
當索引爲負數時,會拋出 java.lang.ArrayIndexOutOfBoundsException 異常。當索引大於集合長度時,會拋出 IndexOutOfBoundsException 異常。
1 public E get(int index) { 2 rangeCheck(index); 3 4 return elementData(index); 5 }
同理,首先仍是判斷給定索引的合理性,而後直接返回處於該下標位置的數組元素。
1 public int indexOf(Object o) { 2 if (o == null) { 3 for (int i = 0; i < size; i++) 4 if (elementData[i]==null) 5 return i; 6 } else { 7 for (int i = 0; i < size; i++) 8 if (o.equals(elementData[i])) 9 return i; 10 } 11 return -1; 12 }
注意:indexOf(Object o) 方法是返回第一次出現該元素的下標,若是沒有則返回 -1。
還有 lastIndexOf(Object o) 方法是返回最後一次出現該元素的下標。
前面咱們介紹查找元素時,知道能夠經過get(int index)方法,根據索引查找元素,那麼遍歷同理:
1 ArrayList list = new ArrayList(); 2 list.add("a"); 3 list.add("b"); 4 list.add("c"); 5 for(int i = 0 ; i < list.size() ; i++){ 6 System.out.print(list.get(i)+" "); 7 }
先看看具體用法:
1 ArrayList<String> list = new ArrayList<>(); 2 list.add("a"); 3 list.add("b"); 4 list.add("c"); 5 Iterator<String> it = list.iterator(); 6 while(it.hasNext()){ 7 String str = it.next(); 8 System.out.print(str+" "); 9 }
在介紹 ArrayList 時,咱們知道該類實現了 List 接口,而 List 接口又繼承了 Collection 接口,Collection 接口又繼承了 Iterable 接口,該接口有個 Iterator<T> iterator() 方法,能獲取 Iterator 對象,能用該對象進行集合遍歷,爲何能用該對象進行集合遍歷?咱們再看看 ArrayList 類中的該方法實現:
1 public Iterator<E> iterator() { 2 return new Itr(); 3 }
該方法是返回一個 Itr 對象,這個類是 ArrayList 的內部類。
1 private class Itr implements Iterator<E> { 2 int cursor; //遊標, 下一個要返回的元素的索引 3 int lastRet = -1; // 返回最後一個元素的索引; 若是沒有這樣的話返回-1. 4 int expectedModCount = modCount; 5 6 //經過 cursor != size 判斷是否還有下一個元素 7 public boolean hasNext() { 8 return cursor != size; 9 } 10 11 @SuppressWarnings("unchecked") 12 public E next() { 13 checkForComodification();//迭代器進行元素迭代時同時進行增長和刪除操做,會拋出異常 14 int i = cursor; 15 if (i >= size) 16 throw new NoSuchElementException(); 17 Object[] elementData = ArrayList.this.elementData; 18 if (i >= elementData.length) 19 throw new ConcurrentModificationException(); 20 cursor = i + 1;//遊標向後移動一位 21 return (E) elementData[lastRet = i];//返回索引爲i處的元素,並將 lastRet賦值爲i 22 } 23 24 public void remove() { 25 if (lastRet < 0) 26 throw new IllegalStateException(); 27 checkForComodification(); 28 29 try { 30 ArrayList.this.remove(lastRet);//調用ArrayList的remove方法刪除元素 31 cursor = lastRet;//遊標指向刪除元素的位置,原本是lastRet+1的,這裏刪除一個元素,而後遊標就不變了 32 lastRet = -1;//lastRet恢復默認值-1 33 expectedModCount = modCount;//expectedModCount值和modCount同步,由於進行add和remove操做,modCount會加1 34 } catch (IndexOutOfBoundsException ex) { 35 throw new ConcurrentModificationException(); 36 } 37 } 38 39 @Override 40 @SuppressWarnings("unchecked") 41 public void forEachRemaining(Consumer<? super E> consumer) {//便於進行forEach循環 42 Objects.requireNonNull(consumer); 43 final int size = ArrayList.this.size; 44 int i = cursor; 45 if (i >= size) { 46 return; 47 } 48 final Object[] elementData = ArrayList.this.elementData; 49 if (i >= elementData.length) { 50 throw new ConcurrentModificationException(); 51 } 52 while (i != size && modCount == expectedModCount) { 53 consumer.accept((E) elementData[i++]); 54 } 55 // update once at end of iteration to reduce heap write traffic 56 cursor = i; 57 lastRet = i - 1; 58 checkForComodification(); 59 } 60 61 //前面在新增元素add() 和 刪除元素 remove() 時,咱們能夠看到 modCount++。修改set() 是沒有的 62 //也就是說不能在迭代器進行元素迭代時進行增長和刪除操做,不然拋出異常 63 final void checkForComodification() { 64 if (modCount != expectedModCount) 65 throw new ConcurrentModificationException(); 66 } 67 }
注意在進行 next() 方法調用的時候,會進行 checkForComodification() 調用,該方法表示迭代器進行元素迭代時,若是同時進行增長和刪除操做,會拋出 ConcurrentModificationException 異常。好比:
1 ArrayList<String> list = new ArrayList<>(); 2 list.add("a"); 3 list.add("b"); 4 list.add("c"); 5 Iterator<String> it = list.iterator(); 6 while(it.hasNext()){ 7 String str = it.next(); 8 System.out.print(str+" "); 9 list.remove(str);//集合遍歷時進行刪除或者新增操做,都會拋出 ConcurrentModificationException 異常 10 //list.add(str); 11 list.set(0, str);//修改操做不會形成異常 12 }
解決辦法是不調用 ArrayList.remove() 方法,轉而調用 迭代器的 remove() 方法:
1 Iterator<String> it = list.iterator(); 2 while(it.hasNext()){ 3 String str = it.next(); 4 System.out.print(str+" "); 5 //list.remove(str);//集合遍歷時進行刪除或者新增操做,都會拋出 ConcurrentModificationException 異常 6 it.remove(); 7 }
注意:迭代器只能向後遍歷,不能向前遍歷,可以刪除元素,可是不能新增元素。
1 ArrayList<String> list = new ArrayList<>(); 2 list.add("a"); 3 list.add("b"); 4 list.add("c"); 5 for(String str : list){ 6 System.out.print(str + " "); 7 }
這種語法能夠當作是 JDK 的一種語法糖,經過反編譯 class 文件,咱們能夠看到生成的 java 文件,其具體實現仍是經過調用 Iterator 迭代器進行遍歷的。以下:
1 ArrayList list = new ArrayList(); 2 list.add("a"); 3 list.add("b"); 4 list.add("c"); 5 String str; 6 for (Iterator iterator1 = list.iterator(); iterator1.hasNext(); System.out.print((new StringBuilder(String.valueOf(str))).append(" ").toString())) 7 str = (String)iterator1.next();
仍是先看看具體用法:
1 ArrayList<String> list = new ArrayList<>(); 2 list.add("a"); 3 list.add("b"); 4 list.add("c"); 5 ListIterator<String> listIt = list.listIterator(); 6 7 //向後遍歷 8 while(listIt.hasNext()){ 9 System.out.print(listIt.next()+" ");//a b c 10 } 11 12 //向後前遍歷,此時因爲上面進行了向後遍歷,遊標已經指向了最後一個元素,因此此處向前遍歷能有值 13 while(listIt.hasPrevious()){ 14 System.out.print(listIt.previous()+" ");//c b a 15 }
還能一邊遍歷,一邊進行新增或者刪除操做:
1 ArrayList<String> list = new ArrayList<>(); 2 list.add("a"); 3 list.add("b"); 4 list.add("c"); 5 ListIterator<String> listIt = list.listIterator(); 6 7 //向後遍歷 8 while(listIt.hasNext()){ 9 System.out.print(listIt.next()+" ");//a b c 10 listIt.add("1");//在每個元素後面增長一個元素 "1" 11 } 12 13 //向後前遍歷,此時因爲上面進行了向後遍歷,遊標已經指向了最後一個元素,因此此處向前遍歷能有值 14 while(listIt.hasPrevious()){ 15 System.out.print(listIt.previous()+" ");//1 c 1 b 1 a 16 }
也就是說相比於 Iterator 迭代器,這裏的 ListIterator 多出了能向前迭代,以及可以新增元素。下面咱們看看具體實現:
對於 Iterator 迭代器,咱們查看 JDK 源碼,發現還有 ListIterator 接口繼承了 Iterator:
public interface ListIterator<E> extends Iterator<E>
該接口有以下方法:
咱們看在 ArrayList 類中,有以下方法能夠得到 ListIterator 接口:
public ListIterator<E> listIterator() { return new ListItr(0); }
這裏的 ListItr 也是一個內部類。
1 //注意 內部類 ListItr 繼承了另外一個內部類 Itr 2 private class ListItr extends Itr implements ListIterator<E> { 3 ListItr(int index) {//構造函數,進行遊標初始化 4 super(); 5 cursor = index; 6 } 7 8 public boolean hasPrevious() {//判斷是否有上一個元素 9 return cursor != 0; 10 } 11 12 public int nextIndex() {//返回下一個元素的索引 13 return cursor; 14 } 15 16 public int previousIndex() {//返回上一個元素的索引 17 return cursor - 1; 18 } 19 20 //該方法獲取當前索引的上一個元素 21 @SuppressWarnings("unchecked") 22 public E previous() { 23 checkForComodification();//迭代器進行元素迭代時同時進行增長和刪除操做,會拋出異常 24 int i = cursor - 1; 25 if (i < 0) 26 throw new NoSuchElementException(); 27 Object[] elementData = ArrayList.this.elementData; 28 if (i >= elementData.length) 29 throw new ConcurrentModificationException(); 30 cursor = i;//遊標指向上一個元素 31 return (E) elementData[lastRet = i];//返回上一個元素的值 32 } 33 34 35 public void set(E e) { 36 if (lastRet < 0) 37 throw new IllegalStateException(); 38 checkForComodification(); 39 40 try { 41 ArrayList.this.set(lastRet, e); 42 } catch (IndexOutOfBoundsException ex) { 43 throw new ConcurrentModificationException(); 44 } 45 } 46 47 //相比於迭代器 Iterator ,這裏多了一個新增操做 48 public void add(E e) { 49 checkForComodification(); 50 51 try { 52 int i = cursor; 53 ArrayList.this.add(i, e); 54 cursor = i + 1; 55 lastRet = -1; 56 expectedModCount = modCount; 57 } catch (IndexOutOfBoundsException ex) { 58 throw new ConcurrentModificationException(); 59 } 60 } 61 }
在 ArrayList 中有這樣一個方法:
1 public List<E> subList(int fromIndex, int toIndex) { 2 subListRangeCheck(fromIndex, toIndex, size); 3 return new SubList(this, 0, fromIndex, toIndex); 4 }
做用是返回從 fromIndex(包括) 開始的下標,到 toIndex(不包括) 結束的下標之間的元素視圖。以下:
1 ArrayList<String> list = new ArrayList<>(); 2 list.add("a"); 3 list.add("b"); 4 list.add("c"); 5 6 List<String> subList = list.subList(0, 1); 7 for(String str : subList){ 8 System.out.print(str + " ");//a 9 }
這裏出現了 SubList 類,這也是 ArrayList 中的一個內部類。
注意:返回的是原集合的視圖,也就是說,若是對 subList 出來的集合進行修改或新增操做,那麼原始集合也會發生一樣的操做。
1 ArrayList<String> list = new ArrayList<>(); 2 list.add("a"); 3 list.add("b"); 4 list.add("c"); 5 6 List<String> subList = list.subList(0, 1); 7 for(String str : subList){ 8 System.out.print(str + " ");//a 9 } 10 subList.add("d"); 11 System.out.println(subList.size());//2 12 System.out.println(list.size());//4,原始集合長度也增長了
想要獨立出來一個集合,解決辦法以下:
List<String> subList = new ArrayList<>(list.subList(0, 1));
public int size() { return size; }
注意:返回集合的長度,而不是數組的長度,這裏的 size 就是定義的全局變量。
1 public boolean isEmpty() { 2 return size == 0; 3 }
返回 size == 0 的結果。
1 public void trimToSize() { 2 modCount++; 3 if (size < elementData.length) { 4 elementData = Arrays.copyOf(elementData, size); 5 } 6 }
該方法用於回收多餘的內存。也就是說一旦咱們肯定集合不在添加多餘的元素以後,調用 trimToSize() 方法會將實現集合的數組大小恰好調整爲集合元素的大小。
注意:該方法會花時間來複制數組元素,因此應該在肯定不會添加元素以後在調用。
參考文檔:https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#