arraylist是如何扩容的?_ArrayList的源码分析
ArrayList的類定義
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable{實現 List 接口,繼承了 AbstractList 抽象類,這個是很好理解的,ArrayList作為List集合類家族的一部分,可以更好的將公用方法抽象給上層
實現Cloneable和Serializable接口?,可以進行克隆和序列化操作
實現RandomAccess接口,這個接口會相對陌生點,點進去官方給的解釋是,RandomAccess是一個標記接口,是一個空接口,僅起到標記的作用(類似Serializable接口)
public interface RandomAccess {}List實現這個接口表明這個類能實現?快速隨機訪問。該接口的主要目的是允許通用算法更改其行為,以便在應用于隨機訪問或順序訪問列表時提供良好的性能
而后文檔里給了說明,如果實現了RandomAccess接口,對List做查詢操作時使用for循環的方式,否則使用迭代器的方式。這樣做的原因做了RandomAccess接口標記的LIst實現類底層數據結構使用for循環效率更高(比如ArrayList),沒有RandomAccess接口標記的使用迭代器效率更高(比如LinkedList,下文可以看到LinkedList沒有實現RandomAccess接口)
舉個簡單的小例子,比如Collections類里的二分查找方法,就是用是否標記了RandomAccess接口來區分用哪種方法實現的:
public static int binarySearch(List extends Comparable super T>> list, T key) { if (list instanceof RandomAccess || list.size() return Collections.indexedBinarySearch(list, key); // for循環方法 else return Collections.iteratorBinarySearch(list, key); // Iterator循環方法}ArrayList構造函數
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};ArrayList實現了三種構造函數用于不同情形下的對象創建
a. 無參構造了一個空數組,在首次添加元素的時候才給數據設置大小的構造函數
b.?給定初始容量的構造函數,通過參數 initialCapacity 可設置List數組的初始大小,這樣做的好處是當 ArrayList 新增元素時,如果所存儲的元素已經超過其已有大小,它會計算元素大小后再進行動態擴容,數組的擴容會導致整個數組進行一次內存復制。因此,我們在初始化 ArrayList 時,可以通過第一個構造函數合理指定數組初始大小,這樣有助于減少數組的擴容次數,從而提高系統性能
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { // initialCapacity大于0,數組的大小為initialCapacity值 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { // initialCapacity等于0,生成默認空數組EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } else { // 其他情況,報非法參數異常-》數組的大小必須大于等于0 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); }}c.傳入Collection對象轉化成ArrayList,將 Collection 轉化為數組并賦值給 elementData,把 elementData 中元素的個數賦值給 size。如果 size 不為零,則判斷 elementData 的 class 類型是否為 Object[],不是的話則做一次轉換。如果 size 為零,則把 EMPTY_ELEMENTDATA 賦值給 elementData,相當于new ArrayList(0)
public ArrayList(Collection extends E> c) { Object[] a = c.toArray(); if ((size = a.length) != 0) { if (c.getClass() == ArrayList.class) { elementData = a; } else { elementData = Arrays.copyOf(a, size, Object[].class); } } else { // replace with empty array. elementData = EMPTY_ELEMENTDATA; }}ArrayList屬性
transient Object[] elementData; // non-private to simplify nested class accessprivate static final int DEFAULT_CAPACITY = 10;private int size;elementData?:底層數組,用來存儲數據
DEFAULT_CAPACITY?:數組的默認初始化容量 10
size?:當前的數組大小,用來表示當前數組包含了多少個元素
ArrayList新增元素
/** * 直接將元素添加到數組尾部 */public boolean add(E e) { // 1.判斷當前數組容量是否夠用,不夠的話進行數組擴容操作 ensureCapacityInternal(size + 1); // Increments modCount!! // 2.將元素添加到數組尾部,size加1 elementData[size++] = e; return true;}/** * 將元素添加到指定位置 */public void add(int index, E element) { // 1.判斷指定位置是否在數組包含范圍內 rangeCheckForAdd(index); // 2.判斷當前數組容量是否夠用,不夠的話進行數組擴容操作 ensureCapacityInternal(size + 1); // Increments modCount!! // 3.將指定位置開始的元素向后挪動一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 4.將元素添加到指定位置上 elementData[index] = element; size++;}ArrayList提供了兩種添加元素的方法,第一種:直接將元素添加到數組尾部;第二種:將元素添加到指定位置
從代碼里可以看出,兩種添加方式都執行了 ensureCapacityInternal 方法,判斷數組的容量情況,具體看下這個方法是如何實現的呢:
判斷當前數組是否是空數組EMPTY_ELEMENTDATA,如果是的話,判斷默認值(10)和傳入的容量(size+1)誰大,就返回哪個,如果不是空數組的話,直接返回(size+1),這一步是對初始化為空數組的情況進行特殊處理
判斷傳入容量(size+1)是否大于當前的數組大小,如果是的話進行擴容操作(grow)
擴容操作是將容量擴充到原始容量的1.5倍大小,如果還是不夠,就將容量給定為當前傳入的容量(size+1)
這里需要判斷下是否有內存溢出的問題,當容量達到允許的最大值(MAX_ARRAY_SIZE)的時候,將數組容量給定為int最大值
擴容后需要將原數組重新分配到新的內存地址中
/** * 計算數組容量,從傳入的容量和默認容量(10)里面去較大的 */private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity;}/** * 計算數組容量,從傳入的容量和默認容量(10)里面去較大的 */private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}/** * 計算數組容量,如果傳入容量比當前數組已有元素數量小,需要進行擴容操作 */private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity);}/** * 數組擴容 */private void grow(int minCapacity) { // 計算擴容后的數組容量 // overflow-conscious code int oldCapacity = elementData.length; // 擴容后新的容量是原容量的1.5倍 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);}/** * 數據容量大小邊界處理 */private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;}/** * 數據允許的最大容量 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;從代碼里可以看到,添加元素到任意位置,會導致在該位置后的所有元素都需要重新排列,而將元素添加到數組的末尾,在沒有發生擴容的前提下,是不會有元素復制排序過程的。所以直接將元素添加的數組的末尾效率會更高
ArrayList查找元素
public E get(int index) { // 檢查index是否越界, rangeCheck(index); // 返回數組對應下標元素值 return elementData(index);}private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}@SuppressWarnings("unchecked")E elementData(int index) { return (E) elementData[index];}ArrayList在數據查找上的效率很高,只需要O(1)的時間復雜度就能獲取數據,傳入需要元素的下標index,返回數據中的對應元素即可
ArrayList刪除元素
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;}ArrayList刪除元素的代碼邏輯和添加元素到任意位置的方法類似,在每一次有效的刪除元素操作之后,都要進行數組的重組,并且刪除的元素位置越靠前,數組重組的開銷就越大
判斷index是否合法(有沒有下標越界)
獲取要刪除的元素
將要刪除數據下標往后的元素向前移動一位
將最后一位置零
返回被刪除的元素
今天的分享就到這里,一起學技術,一起在生活中探險~歡迎關注【小肖愛吃肉】
總結
以上是生活随笔為你收集整理的arraylist是如何扩容的?_ArrayList的源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: servlet指定时间到现在过了多久_就
- 下一篇: python链接mysql 判断是否成功