ArrayList源碼分析

ArrayList結構和派系

派生關係

Iterablejava

集合的最上級接口, 定義了spliterator,iterator和實現了forEach函數

Collection算法

全部集合的父級接口, 定義了通用函數

AbstractCollection數組

實現了Collection中大量函數, 除了特定的幾個函數iterator和size以外的函數

AbstractList數據結構

繼承了AbstractCollection, 實現了List接口的抽象類, 它實現了List中除size(), get(int location)以外的函數
和AbstractCollection相比, 實現了iterator()接口

RandomAccessdom

提供了隨機訪問(隨機查找)功能, 在ArrayList中經過元素的序號快速獲取元素對象, 這就是快速隨機訪問(隨機查找)

Listide

List有序隊列接口, 提供了一些經過下標訪問元素的函數.    
List中每個元素都有一個索引, 第一個元素是0, 日後每次+1

數據結構

ArrayList數據結構是數組
經過必定的算法邏輯動態擴容數組的長度, 能夠理解爲ArrayList底層是一個動態數組
與java原生的數組相比, 它的容量能動態增加  
內存須要連續的空間保證
除去尾部位置元素的添加, 刪除操做, 其他都涉及到位置移動
隨機查找效率快(下標搜尋)

初始化

ArrayList源碼設計的優雅之道
new ArrayList的時候, 並不會真的建立長度10的對象數組
何時往裏面添加元素了,何時纔會完成數組的建立

初始化源碼

private static final long serialVersionUID = 8683452581122892189L;
  
    private static final int DEFAULT_CAPACITY = 10;
 
    private static final Object[] EMPTY_ELEMENTDATA = {};

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    transient Object[] elementData; // non-private to simplify nested class access

    private int size;

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }    
    
    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);
        }
    }

詳細分析

serialVersionUID函數

用來驗證版本一致性的字段, 反序列化時會效驗

DEFAULT_CAPACITY源碼分析

默認容量, 爲10

EMPTY_ELEMENTDATA測試

空對象數組

DEFAULTCAPACITY_EMPTY_ELEMENTDATAui

默認容量空對象數組

elementData

動態數組的空間大小, 包含空元素

size

動態數組的實際大小, 記錄數組裏有多少元素, 不記錄空元素

ArrayList()

無參構造
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}
把默認容量空對象數組賦值給elementData, 仍是空
無參構造建立動態數組時, size爲0, elementData爲0

ArrayList(int initialCapacity)

帶參構造
參數大於0時
    this.elementData = new Object[initialCapacity];
    建立一個大小爲initialCapacity的Object數組賦給elementData
參數小與於0時
    this.elementData = EMPTY_ELEMENTDATA;
    開一個長度爲10的空間
參數小與於0時
    拋異常

總結

代碼測試

public static void main(String[] args) throws Exception {
        ArrayList  arr = new ArrayList();
        Field field = arr.getClass().getDeclaredField("elementData");
        field.setAccessible(true);
        Object[] o = (Object[]) field.get(arr);
        System.out.println(arr.size());
        System.out.println(o.length);

        arr.add("1");
        field = arr.getClass().getDeclaredField("elementData");
        field.setAccessible(true);
        o= (Object[]) field.get(arr);
        System.out.println(arr.size());
        System.out.println(o.length);

        for (int i = 0; i < 16; i++) {
            arr.add(i);
        }
        field = arr.getClass().getDeclaredField("elementData");
        field.setAccessible(true);
        o= (Object[]) field.get(arr);
        System.out.println(arr.size());
        System.out.println(o.length);
    }

輸出

0
0
1
10
17
22

初始化總結

ArrayList  arr = new ArrayList();
elementData爲0
size爲0

ArrayList  arr = new ArrayList(1);
elementData爲0
size爲1, 走了帶參構造, 參數爲幾, 空間爲幾, ArrayList(int initialCapacity)   

add("test");//添加元素源碼分析會講爲何空間爲10
elementData爲10 
size爲1
若是第一次添加元素數量爲11, 基於動態擴容1.5倍, 它的長度就是15

添加元素

添加元素源碼

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }    

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

詳細分析

添加一個元素1-10個元素時

add(E e)方法 //以無參初始化(ArrayList arr = new ArrayList();), 並調用add(E e)添加一個元素爲例
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    
    進ensureCapacityInternal(size + 1);此時傳參爲1 (0+1=1)
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
   
    進calculateCapacity, 判斷elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA是否成立
    public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
    條件成立, 由於無參初始化方法已經賦過值
    進if, 判斷10和參數哪一個大, 10大, 返回10
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
  
    此時返回ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); 這裏, 參數是10
    10-10不大於0, 沒走到動態擴容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
目前是elementData(空間長度爲10), size爲1(size不記錄空元素)

添加元素超出10的狀況, 動態擴容

添加第11個元素時
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    此時minCapacity爲11
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
   
    不走if, 返回11, 則ensureExplicitCapacity(11)
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
   
    11-10大於0, 進入動態擴容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
         // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    進grow(minCapacity); 動態擴容方法
    private void grow(int minCapacity) {
        老空間爲10
        int oldCapacity = elementData.length;
        新空間=10+(10>>1), 位運算10的二進制位爲1010, 
        右移動1位二進制爲101, 10進製爲5, 因此新空間爲15=10+5
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        若是新空間-11小於0,則新空間爲11 
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        若是新空間大於0, 則賦值
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity); //下面講這個方法
        拷貝一個新的數組和長度的集合,賦值給咱們的對象數組
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    
    MAX_ARRAY_SIZE爲Integer.MAX_VALUE-8 (int最大值-8)
    若是要開的空間大於MAX_ARRAY_SIZE, 就把int最大值-8賦值給它
    若是不大於MAX_ARRAY_SIZE,則把MAX_ARRAY_SIZE賦值給它
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    
    
    官方註釋對於-8的介紹
        這8bit裏存了要分配最大數組的大小(數組下標???)
        虛擬機中在數據中保留的標題字 (既數組形狀判斷是否爲數組,對象是否同步,數組大小, 不知道是否包含了對象類型的類信息指針???)
        嘗試分配更大的數組會拋異常, 其實大小仍是支持到了Integer.MAX_VALUE
    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

fail-fast失敗緣由(modCount++)

以前介紹源碼的時候把modCount++跳過了, 接下來解釋下, 在ArrayList源碼裏有個監聽
源碼

private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
在每次作集合元素操做時,都會改變modCount,在迭代的時候,會把modCount賦值給expectedModCount,      
若是在迭代過程當中刪除元素,就會修改modCount,可是迭代器在過程當中不會同步expectedModCount,  
 每次迭代會會比較是否相等
 就是說在使用迭代器的時候, 不能改變元素, 可是能夠使用迭代器的方法去改變

查找元素

public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    E elementData(int index) {
        return (E) elementData[index];
    }
判斷下標是否越界
複雜度O(1), 直接根據下標位置返回元素

刪除元素

刪除元素源碼

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;
    }
    
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
                                        
    src - 源數組。
    srcPos - 源數組中的起始位置。
    dest - 目標數組。
    destPos - 目的地數據中的起始位置。
    length - 要複製的數組元素的數量。

總結

將指定源數組中的數組從指定位置複製到目標數組的指定位置

好比咱們有個數組
    值爲 a b c d e f g h i g k
    下標 0 1 2 3 4 5 6 7 8 9 10
    若是刪除f
    rangeCheck(index); //效驗下標越界, index=5
    int numMoved = size - index - 1; //numMoved= 4
    調用System.arraycopy(elementData, index+1, elementData, index, numMoved);

從原數組下標從6開始copy到新數組從下標從5開始的後面

相關文章
相關標籤/搜索