本篇分析ArrayList的源碼,在分析之前先跟大家談一談數組。數組可能是我們最早接觸到的數據結構之一,它是在內存中劃分出一塊連續的地址空間用來進行元素的存儲,由於它直接操作內存,所以數組的性能要比集合類更好一些,這是使用數組的一大優勢。但是我們知道數組存在致命的缺陷,就是在初始化時必須指定數組大小,並且在後續操作中不能再更改數組的大小。在實際情況中我們遇到更多的是一開始並不知道要存放多少元素,而是希望容器能夠自動的擴展它自身的容量以便能夠存放更多的元素。 ArrayList就能夠很好的滿足這樣的需求,它能夠自動擴展大小以適應存儲元素的不斷增加。它的底層是基於數組實現的,因此它具有數組的一些特點,例如查找修改快而插入刪除慢。本篇我們將深入源碼看看它是怎樣對數組進行封裝的。首先看看它的成員變量和三個主要的構造器。
//默認初始化容量private static final int DEFAULT_CAPACITY = 10;//空對像數組private static final Object[] EMPTY_ELEMENTDATA = {};//對像數組private transient Object[] elementData;//集合元素個數private int size;//傳入初始容量的構造方法public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } //新建指定容量的Object類型數組this.elementData = new Object[initialCapacity];}//不帶參數的構造方法public ArrayList() { super(); //將空的數組實例傳給elementData this.elementData = EMPTY_ELEMENTDATA;}//傳入外部集合的構造方法public ArrayList(Collection<? extends E> c) { //持有傳入集合的內部數組的引用elementData = c.toArray(); //更新集合元素個數大小size = elementData.length; //判斷引用的數組類型, 並將引用轉換成Object數組引用if (elementData.getClass() != Object[].class) { elementData = Arrays.copyOf(elementData, size, Object[].class); }}可以看到ArrayList的內部存儲結構就是一個Object類型的數組,因此它可以存放任意類型的元素。在構造ArrayList的時候,如果傳入初始大小那麼它將新建一個指定容量的Object數組,如果不設置初始大小那麼它將不會分配內存空間而是使用空的對像數組,在實際要放入元素時再進行內存分配。下面再看看它的增刪改查方法。
//增(添加)public boolean add(E e) { //添加前先檢查是否需要拓展數組, 此時數組長度最小為size+1 ensureCapacityInternal(size + 1); //將元素添加到數組末尾elementData[size++] = e; return true;}//增(插入)public void add(int index, E element) { //插入位置範圍檢查rangeCheckForAdd(index); //檢查是否需要擴容ensureCapacityInternal(size + 1); //挪動插入位置後面的元素System.arraycopy(elementData, index, elementData, index + 1, size - index); //在要插入的位置賦上新值elementData[index] = element; size++;}//刪public E remove(int index) { //index不能大於size rangeCheck(index); 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; return oldValue;}//改public E set(int index, E element) { //index不能大於size rangeCheck(index); E oldValue = elementData(index); //替換成新元素elementData[index] = element; return oldValue;}//查public E get(int index) { //index不能大於size rangeCheck(index); //返回指定位置元素return elementData(index);}每次添加一個元素到集合中都會先檢查容量是否足夠,否則就進行擴容,擴容的細節下面會講到。我們先看具體增刪改查要注意的地方。
增(添加):僅是將這個元素添加到末尾。操作快速。
增(插入):由於需要移動插入位置後面的元素,並且涉及數組的複制,所以操作較慢。
刪:由於需要將刪除位置後面的元素向前挪動,也會設計數組複製,所以操作較慢。
改:直接對指定位置元素進行修改,不涉及元素挪動和數組複製,操作快速。
查:直接返回指定下標的數組元素,操作快速。
通過源碼看到,由於查找和修改直接定位到數組下標,不涉及元素挪動和數組複製所以較快,而插入刪除由於要挪動元素,涉及到數組複製,操作較慢。並且每次添加操作還可能進行數組擴容,也會影響到性能。下面我們看看ArrayList是怎樣動態擴容的。
private void ensureCapacityInternal(int minCapacity) { //如果此時還是空數組if (elementData == EMPTY_ELEMENTDATA) { //和默認容量比較, 取較大值minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } //數組已經初始化過就執行這一步ensureExplicitCapacity(minCapacity);}private void ensureExplicitCapacity(int minCapacity) { modCount++; //如果最小容量大於數組長度就擴增數組if (minCapacity - elementData.length > 0) { grow(minCapacity); }}//集合最大容量private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//增加數組長度private void grow(int minCapacity) { //獲取數組原先的容量int oldCapacity = elementData.length; //新數組的容量, 在原來的基礎上增加一半int newCapacity = oldCapacity + (oldCapacity >> 1); //檢驗新的容量是否小於最小容量if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } //檢驗新的容量是否超過最大數組容量if (newCapacity - MAX_ARRAY_SIZE > 0) { newCapacity = hugeCapacity(minCapacity); } //拷貝原來的數組到新數組elementData = Arrays.copyOf(elementData, newCapacity);}每次添加元素前會調用ensureCapacityInternal這個方法進行集合容量檢查。在這個方法內部會檢查當前集合的內部數組是否還是個空數組,如果是就新建默認大小為10的Object數組。如果不是則證明當前集合已經被初始化過,那麼就調用ensureExplicitCapacity方法檢查當前數組的容量是否滿足這個最小所需容量,不滿足的話就調用grow方法進行擴容。在grow方法內部可以看到,每次擴容都是增加原來數組長度的一半,擴容實際上是新建一個容量更大的數組,將原先數組的元素全部複製到新的數組上,然後再拋棄原先的數組轉而使用新的數組。至此,我們對ArrayList中比較常用的方法做了分析,其中有些值得注意的要點:
1. ArrayList底層實現是基於數組的,因此對指定下標的查找和修改比較快,但是刪除和插入操作比較慢。
2. 構造ArrayList時盡量指定容量,減少擴容時帶來的數組複製操作,如果不知道大小可以賦值為默認容量10。
3. 每次添加元素之前會檢查是否需要擴容,每次擴容都是增加原有容量的一半。
4. 每次對下標的操作都會進行安全性檢查,如果出現數組越界就立即拋出異常。
5. ArrayList的所有方法都沒有進行同步,因此它不是線程安全的。
6. 以上分析基於JDK1.7,其他版本會有些出入,因此不能一概而論。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。