javase基礎回顧(一)ArrayList深入解析 解讀ArrayList原始碼(JDK1.8.0_92),javasejdk1.8.0_92
我們在學習這一塊內容時需要注意的一個問題是 集合中存放的依然是對象的引用而不是對象本身。
List介面擴充了Collection並聲明儲存一系列元素的類集的特性。使用一個基於零的下標,元素可以通過它們在列表中的位置被插入和訪問。一個列表可以包含重複元素。List在集合中是一個比較重要的知識點也是在開發中最常用的。
我們都知道ArrayList是由數組實現的,但是和數組有很大區別的是隨著向ArrayList中不斷添加元素,其容量也自動成長,而數組聲明好之後其容量就不會改變。想要探明其中的究竟探析其中的原理十分重要,今天重新看了一下這塊的原始碼(JDK1.8.0_92)感覺很有收穫,所以在此記錄和分享。
1.Arraylist類中的屬性
1 public class ArrayList<E> extends AbstractList<E> 2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable 3 { 4 private static final long serialVersionUID = 8683452581122892189L; 5 6 /** 7 *預設初始容量 8 */ 9 private static final int DEFAULT_CAPACITY = 10;10 11 /**12 *被用於空執行個體的共用空數組執行個體13 */14 private static final Object[] EMPTY_ELEMENTDATA = {};15 16 /**17 * Object[]類型的數組,儲存了添加到ArrayList中的元素。ArrayList的容量是該Object[]類型數組的長度18 * 當第一個元素被添加時,任何空ArrayList中的elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA將會被19 * 擴充到DEFAULT_CAPACITY(預設容量)。20 */21 private transient Object[] elementData;22 23 /**24 * ArrayList的大小(其實就是size()方法返回的那個值)25 *26 * @serial27 */28 private int size;29 30 ......31 32 }類屬性
在這裡需要注意的有幾點:
DEFAULT_CAPACITY 這個變數指的是ArrayList預設的容量,其實剛剛初始化一個Arraylist其容量是0,當添加一個之後容量就變成了10,在jdk1.6版本的時候還不是這麼處理的。接下來會一一介紹。
elementData 這個變數是一個數組,在JDK1.8.0_92的原始碼的註解中很清晰的說明了這個數組是用來緩衝ArrayList裡的資料(這裡的資料指的是對象的引用),ArrayList的大小取決於這個緩衝數組的長度,還指明了一點就是在初始化的時候這個緩衝數組的是一個空數組當第一次添加的時候會把這個緩衝數組的長度擴充為上面的DEFAULT_CAPACITY也就是10。
size 這個變數就指的是緩衝數組的大小也就是ArrayList的長度。
2.構造方法
1 //帶參數的構造方法 參數時ArrayList的初始長度 2 public ArrayList(int initialCapacity) { 3 super(); 4 if (initialCapacity < 0) 5 throw new IllegalArgumentException("Illegal Capacity: "+ 6 initialCapacity); 7 this.elementData = new Object[initialCapacity]; 8 } 9 //不帶參數的構造方法(初始化的長度為0) 也是我們最常用的構造方法10 public ArrayList() {11 super();12 this.elementData = EMPTY_ELEMENTDATA;13 }14 //帶參數的構造方法 構造一個包含指定collection的元素的列表,這些元素按照該collection的迭代器返回的順序排列的15 public ArrayList(Collection<? extends E> c) {16 elementData = c.toArray();17 size = elementData.length;18 // c.toArray might (incorrectly) not return Object[] (see 6260652)19 if (elementData.getClass() != Object[].class)20 elementData = Arrays.copyOf(elementData, size, Object[].class);21 }View Code
在這裡需要注意的是,在不同版本的jdk中此處的實現機制略有不同。如下:
在jdk1.8.0_45中不帶參數的構造方法:
1 public ArrayList() {2 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}3 }View Code
在JDK1.6中不帶參數的構造方法:
1 public ArrayList() { 2 this(10); //public ArrayList(int initialCapacity)中this.elementData = new Object[initialCapacity];3 }View Code
在JDK1.8.0_92和jdk1.8.0_45中代碼雖然略微有些不同但是他們的實現機制是一樣的,都是剛開始的聲明一個空數組是在添加的時候才把數組的長度擴充為10,而在JDK1.6時在剛剛聲明的時候就聲明的長度為10的數組。這也是慢慢在做最佳化吧。
具體是在什麼時候數組長度進行擴充的我們在下邊看
3.添加元素
1 //將指定的元素(E e)添加到此列表的尾部 2 public boolean add(E e) { 3 ensureCapacityInternal(size + 1); // Increments modCount!! 4 elementData[size++] = e; 5 return true; 6 } 7 8 //將指定的元素(E e)插入到列表的指定位置(index) 9 public void add(int index, E element) {10 rangeCheckForAdd(index); //判斷參數index是否IndexOutOfBoundsException11 12 ensureCapacityInternal(size + 1); // Increments modCount!! 如果數組長度不足,將進行擴容13 System.arraycopy(elementData, index, elementData, index + 1,14 size - index); //將源數組中從index位置開始後的size-index個元素統一後移一位15 elementData[index] = element;16 size++;17 }18 19 /**20 * 按照指定collection的迭代器所返回的元素順序,將該collection中的所有元素添加到此列表的尾部21 * @throws NullPointerException if the specified collection is null22 */23 public boolean addAll(Collection<? extends E> c) {24 Object[] a = c.toArray();25 int numNew = a.length;26 ensureCapacityInternal(size + numNew); // Increments modCount27 //將數組a[0,...,numNew-1]複製到數組elementData[size,...,size+numNew-1]28 System.arraycopy(a, 0, elementData, size, numNew); 29 size += numNew;30 return numNew != 0;31 }32 33 /**34 * 從指定的位置開始,將指定collection中的所有元素插入到此列表中,新元素的順序為指定collection的迭代器所返回的元素順序35 * @throws IndexOutOfBoundsException {@inheritDoc}36 * @throws NullPointerException if the specified collection is null37 */38 public boolean addAll(int index, Collection<? extends E> c) {39 rangeCheckForAdd(index); //判斷參數index是否IndexOutOfBoundsException40 41 Object[] a = c.toArray();42 int numNew = a.length;43 ensureCapacityInternal(size + numNew); // Increments modCount44 45 int numMoved = size - index;46 if (numMoved > 0)47 //先將數組elementData[index,...,index+numMoved-1]複製到elementData[index+numMoved,...,index+2*numMoved-1]48 //即,將源數組中從index位置開始的後numMoved個元素統一後移numNew位49 System.arraycopy(elementData, index, elementData, index + numNew,50 numMoved);51 //再將數組a[0,...,numNew-1]複製到數組elementData[index,...,index+numNew-1]52 System.arraycopy(a, 0, elementData, index, numNew);53 size += numNew;54 return numNew != 0;55 }View Code
上面幾個添加方法中具體的實現方法沒有在上邊列出來我放在下邊
具體的實現方法:
1 /** 2 * public方法,讓使用者能手動設定ArrayList的容量 3 * @param minCapacity 期望的最小容量 4 */ 5 public void ensureCapacity(int minCapacity) { 6 int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) 7 // any size if not default element table 8 ? 0 9 // larger than default for default empty table. It's already10 // supposed to be at default size.11 : DEFAULT_CAPACITY;12 13 if (minCapacity > minExpand) {14 ensureExplicitCapacity(minCapacity);15 }16 }17 18 private void ensureCapacityInternal(int minCapacity) {19 //當elementData為空白時,ArrayList的初始容量最小為DEFAULT_CAPACITY(10)20 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {21 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);22 }23 ensureExplicitCapacity(minCapacity);24 }25 26 private void ensureExplicitCapacity(int minCapacity) {27 modCount++;28 // overflow-conscious code29 if (minCapacity - elementData.length > 0)30 grow(minCapacity);31 }32 33 //數組可被分配的最大容量;當需要的數組尺寸超過VM的限制時,可能導致OutOfMemoryError34 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;35 36 /**37 * 增加數組的容量,確保它至少能容納指定的最小容量的元素量38 * @param minCapacity 期望的最小容量39 */40 private void grow(int minCapacity) {41 // overflow-conscious code42 int oldCapacity = elementData.length;43 //注意此處擴充capacity的方式是將其向右移一位再加上原來的數,實際上是擴充了1.5倍44 int newCapacity = oldCapacity + (oldCapacity >> 1);45 if (newCapacity - minCapacity < 0)46 newCapacity = minCapacity;47 if (newCapacity - MAX_ARRAY_SIZE > 0) 48 newCapacity = hugeCapacity(minCapacity); //設定數組可被分配的最大容量49 // minCapacity is usually close to size, so this is a win:50 elementData = Arrays.copyOf(elementData, newCapacity);51 }52 53 private static int hugeCapacity(int minCapacity) {54 if (minCapacity < 0) // overflow55 throw new OutOfMemoryError();56 return (minCapacity > MAX_ARRAY_SIZE) ?57 Integer.MAX_VALUE :58 MAX_ARRAY_SIZE;59 }View Code
在這裡值得注意的是grow()方法中的>> 1,其實該運算就是相當於除以2.例如:
1010 十進位:10 原始數 number10100 十進位:20 左移一位 number = number << 1; 1010 十進位:10 右移一位 number = number >> 1;
所以我們跟進 add(E e)方法就會知道當對ArrayList進行add操作時,最開始的時候進入add()方法,然後執行ensureCapacityInternal()方法,繼續跟進在這裡elementData == EMPTY_ELEMENTDATA為true所以minCapacity = 10。然後minCapacity - elementData.length > 0為true,執行grow()方法,每次執行grow()方法在一般情況下都會產生一個新數組且長度是原數組長度的1.5倍,但是有兩種情況除外第一點就是當新產生的數組長度小於10時 那麼將新產生的數組長度改為10,也就是grow()方法第一個if語句裡的代碼做的事情(就是保證了在添加元素小於8個的時候ArrayList的長度都是10),第二點就是當新產生的數組長度大於Integer.MAX_VALUE - 8時會對數組長度進行調整避免越界(這就是grow()方法第二個if語句裡的代碼做的事情)。這就是ArrayList容量自動擴充的基本原理。
我之前看過jdk1.6在此處的實現方式,略有不同但是也是產生新數組且長度是原數組的1.5倍。至於為什麼是1.5倍我覺得可能是那些大牛以自己的經驗或是經過科學的推導才得到這麼一個最優解,我也不知道了 ,猜的 歡迎補充。
就介紹這麼多吧,ArrayList裡還有很多方法像刪除元素,修改元素還有尋找元素等等比較容易讀懂,就不在這裡都寫出來了。
我們大致瞭解了ArrayList的基本原理然後再去想ArrayList的特點就比較容易懂了。相對於LinkedList,ArrayList比較適用於搜尋操作,LinkedList比較適用於插入或是刪除操作。因為ArrayList適用數組實現的,在記憶體中所儲存的資料是連續的所以很容易從一個元素定位到另一個元素,而LinkedList就不同了,LinkedList適用雙鏈表實現的他就必須挨個一個一個的看是不是要找的元素,但是LinkedList在執行中間插入或是中間刪除操作時效率是很高的。
還有在eclipse中查看原始碼比較常用的快速鍵是Alt + 左右方向鍵,可以實現在之前滑鼠游標處跳轉 非常實用。
歡迎大家查補缺漏!
OutOfMemoryError