ArrayList

ArrayList介紹

ArrayList簡介

  1. ArrayList是一個數組隊列,至關於動態數組。與Java中的數組相比,它的容量能動態增加。它繼承於AbstractList,實現了List,RandomAccess,Cloneable,java.io.Serializable這些接口。
  2. ArrayList與Collection的關係以下圖,實現表明繼承,虛線表明實現接口:
  3. ArrayList繼承了AbstractList,實現了List。它是一個數組隊列,提供了相關的添加、刪除、修改、遍歷等功能。
  4. ArrayList實現了RandmoAccess接口,即提供了隨機訪問的功能。RandmoAccess是java中用來被List實現,爲List提供快速訪問功能的。在ArrayList中,咱們便可以經過元素的序號快速獲取元素對象;這就是快速隨機訪問。
  5. ArrayList實現了Cloneable接口,即覆蓋了函數clone(),能被克隆。
  6. ArrayList實現了java.io.Serializable接口,這意味着ArrayList支持序列化,能經過序列化去傳輸。

注意:ArrayList中的操做不是線程安全的!因此,建議在單線程中使用,多線程狀況下能夠選擇CopyOnWriteArrayList或者使用Collections中的synchronizedList方法將其包裝成一個線程安全的List。java

ArrayList的API

// Collection中定義的API
boolean             add(E object)
boolean             addAll(Collection<? extends E> collection)
void                clear()
boolean             contains(Object object)
boolean             containsAll(Collection<?> collection)
boolean             equals(Object object)
int                 hashCode()
boolean             isEmpty()
Iterator<E>         iterator()
boolean             remove(Object object)
boolean             removeAll(Collection<?> collection)
boolean             retainAll(Collection<?> collection)
int                 size()
<T> T[]             toArray(T[] array)
Object[]            toArray()
// AbstractCollection中定義的API
void                add(int location, E object)
boolean             addAll(int location, Collection<? extends E> collection)
E                   get(int location)
int                 indexOf(Object object)
int                 lastIndexOf(Object object)
ListIterator<E>     listIterator(int location)
ListIterator<E>     listIterator()
E                   remove(int location)
E                   set(int location, E object)
List<E>             subList(int start, int end)
// ArrayList新增的API
Object               clone()
void                 ensureCapacity(int minimumCapacity)
void                 trimToSize()
void                 removeRange(int fromIndex, int toIndex)

ArrayList源碼分析

屬性

ArrayList的主要屬性以下代碼所示:git

//序列化id
private static final long serialVersionUID = 8683452581122892189L;
//容器默認初始化大小
private static final int DEFAULT_CAPACITY = 10;
//一個空對象
private static final Object[] EMPTY_ELEMENTDATA = {};
//一個空對象,若是使用默認構造函數建立ArrayList,則默認對象內容是該值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//ArrayList存放對象的容器,後面的添加、刪除等操做都是基於該屬性來進行操做
transient Object[] elementData;
//當前列表已使用的長度
private int size;
//數組最大長度(2147483639),這裏爲何是Integer.MAX_VALUE - 8是由於有些虛擬機在數組中保留了一些頭部信息,防止內存溢出
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//這個是從AbstractList繼承過來的,表明ArrayList集合修改的次數
protected transient int modCount = 0;

關於Java中transient關鍵字的解釋:github

 咱們都知道一個對象只要實現了Serilizable接口,這個對象就能夠被序列化,java的這種序列化模式爲開發者提供了不少便利,咱們能夠沒必要關係具體序列化的過程,只要這個類實現了Serilizable接口,這個類的全部屬性和方法都會自動序列化。api

 然而在實際開發過程當中,咱們經常會遇到這樣的問題,這個類的有些屬性須要序列化,而其餘屬性不須要被序列化,打個比方,若是一個用戶有一些敏感信息(如密碼,銀行卡號等),爲了安全起見,不但願在網絡操做(主要涉及到序列化操做,本地序列化緩存也適用)中被傳輸,這些信息對應的變量就能夠加上transient關鍵字。換句話說,這個字段的生命週期僅存於調用者的內存中而不會寫到磁盤裏持久化。數組

總之,java 的transient關鍵字爲咱們提供了便利,你只須要實現Serilizable接口,將不須要序列化的屬性前添加關鍵字transient,序列化對象的時候,這個屬性就不會序列化到指定的目的地中。緩存

構造函數

無參構造函數

若是不傳入參數,則使用默認無參構造方法建立ArrayLisy對象,以下:安全

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

注意:此時咱們建立的ArrayList對象中的elementData中的長度是0,size是0,當進行第一次add的時候,elementDate將會變成默認的長度:10。網絡

帶int類型的構造函數

若是傳入參數,則表明指定ArrayList的初始數組長度;傳入參數若是是大於0,則使用用戶的參數初始化;若是參數等於0,則用內部的空對象EMPTY_ELEMENTDATA的地址直接賦值給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);
        }
    }

帶Collection對象的構造函數

  1. 將Collection對象轉換成數組,而後將數組的地址賦值給elementData。
  2. 更新size的值,若是size的值等於0直接將內部空對象EMPTY_ELEMENTDATA的地址賦值給elementData。
  3. 若是size的值大於0,則執行Arrays.copy方法,把Collection對象的內容copy(能夠理解爲深拷貝)到elementData中,而且這些元素是按照該collection的迭代器返回它們的順序排列的。
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

這裏介紹下System.arraycopyArrays.copy方法,由於後分析源碼時會常常用到。app

System.arraycopy方法:它就是從指定的源數組將元素中複製到目標數組,複製從指定的位置開始,到設定的複製長度結束,而後從目標數組的指定起始位置依次插入。

// src 源數組
    // srcPos 源數組要複製的起始位置
    // dest 要賦值到的目標數組
    // destPos 目標數組放置的起始位置
    // length 複製的長度
    // 使用了native關鍵字,說明調用的是其餘語言寫的底層函數
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

Arrays.copy方法:它新建了一個數組而且將原數組的內容拷貝到長度爲newLength的新數組中,而且返回該新數組。

// original 要複製的數組
    // newLength 要返回副本的長度
    // newwType 要返回的副本類型
    // 內部調用了System.arraycopy方法
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

區別:

  1. System.arraycopy須要目標數組,將原數組拷貝到你本身定義的數值裏,並且能夠選擇拷貝的起點和長度以及放入新數組中的位置。
  2. Arrays.copyof是系統自動在內部新建一個數組,調用System.arraycopy將原數組的內容拷貝到長度爲newLength的新數組中,並返回新建的數組。

添加元素

ArrayList提供了add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)、set(int index, E element)這個五個方法來實現ArrayList增長。

add(E e)方法
//官方解釋:將指定的元素追加到列表(elementData)的末尾
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

接下來咱們看ensureCapacityInternal方法,以及它內部調用的方法。

//參數值實際是size+1
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
//這個方式是判斷當前數組是不是個空數組,若是是就返回默認長度10,不然就返回size+1;也就是說若是你是用無參構造函數初始化ArrayList,那麼在第一次調用add方法時,默認長度會變成10
  private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
//這個方法首先將集合修改次數加1,而後判斷數組的長度是否能夠存入下一個元素,若是長度不夠會調用grow方法進行擴容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
//這個方法首先定義數組新的長度爲原來數組長度的1.5倍,若是新長度減去所需數組的最小長度小於0,那麼新長度就等於所需數組最小長度;再下面的判斷是若是新長度大於MAX_ARRAY_SIZE(ArrayList內部定義MAX_ARRAY_SIZE的值是:2147483639)就調用hugeCapacity方法,最後調用Arrays.copyOf將擴容後的新數組地址賦值給elementData
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        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);
    }
//若是擴容長度超過MAX_ARRAY_SIZE,則設置長度爲Integer.MAX_VALUE,但不是能百分百成功的,這取決於虛擬機。(若是咱們能夠在某些虛擬機上能夠避免OutOfMemory,咱們將另外分配Integer.MAX_VALUE,若是你很幸運(取決於虛擬機),咱們將成功)
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

最後總結一下add方法的邏輯:

  1. 確保數組已使用長度(size)加1後能夠存入下一個元素。

  2. 修改次數modCount標識自增1,若是當前數組已使用長度+1後大於當前數組長度,則調用grow方法,擴容數組,grow方法會將當前數組的長度變爲原來容量的1.5倍。

  3. 確保新加的元素有地方存儲後,則將新元素添加到位於size++的位置上。

  4. 返回添加成功的布爾值。

add(int index, E element)

這個方法和上面的add類型,該方法能夠按照元素的位置,指定新元素位置插入。

public void add(int index, E element) {
        //判斷索引位置是否正確
        rangeCheckForAdd(index);
        //擴容檢測
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //對源數組進行復制處理(位移),從index + 1到size - index
        //即向後移動位於當前位置和後面的元素
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //在指定的位置賦值
        elementData[index] = element;
        size++;
    }

該方法首先調用rangeCheckForAdd方法判斷指定的位置小於當前數組的長度而且大於0,不然拋出異常。

private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

第二步調用的ensureCapacityInternal方法和上面的add方法邏輯同樣。

第三步調用System.arraycopy方法把指定下標以及後面的元素所有日後移一位

最後將新的元素放到指定位置(index)上,並將size+1。

addAll(Collection<? extends E> c)
//按照指定的Collection迭代器所返回的順序,依次插入到列表尾部。
    public boolean addAll(Collection<? extends E> c) {
        // 將c轉換爲數組
        Object[] a = c.toArray();
        int numNew = a.length;
        //擴容處理,大小爲size + numNew
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

該方法首先傳過來的Collection集合轉換爲數組,而後作擴容處理,接着使用System.arraycopy把轉換後的數組複製到列表尾部。

addAll(int index, Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c) {
        //判斷索引位置是否正確
        rangeCheckForAdd(index);
        // 將c轉換爲數組
        Object[] a = c.toArray();
        int numNew = a.length;
        //擴容處理,大小爲size + numNew
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //若是插入索引小於列表長度,則將當前索引等於index和大於index的元素日後移numMoved個位置
        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
        //將數組添加到列表尾部
        System.arraycopy(a, 0, elementData, index, numNew);
        //更新列表長度
        size += numNew;
        return numNew != 80;
    }
set(int index, E element)
public E set(int index, E element) {
        //判斷插入位置是否正確,若是大於列表長度會拋出異常
        rangeCheck(index);
        //獲取插入位置的當前元素
        E oldValue = elementData(index);
        //將新的元素替換當前插入位置的元素
        elementData[index] = element;
        //返回插入位置老的值
        return oldValue;
    }

刪除元素

ArrayList提供了外界remove(int index)、remove(Object o)、removeAll(Collection<?> c)、clear()四個方法進行元素的刪除。

remove(int index) //移除指定位置上的元素
public E remove(int index) {
        //判斷刪除位置是否正確,若是大於列表長度會拋出異常
        rangeCheck(index);
        //將集合修改次數加1
        modCount++;
        //獲取當前刪除位置上的元素
        E oldValue = elementData(index);
        //判斷是否刪除的是最後一個元素,若是不是將刪除位置後的元素向左移numMoved個位置
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //將列表最後的元素置爲null,等待垃圾收集器收集
        elementData[--size] = null; // clear to let GC do its work
        //返回刪除位置老的值
        return oldValue;
    }
remove(Object o) //移除指定元素
public boolean remove(Object o) {
        //由於ArrayList容許存在null,因此須要進行null判斷
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    //移除這個位置的元素
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
private void fastRemove(int index) {
        //將集合修改次數加1      
        modCount++;
        //判斷是否刪除的是最後一個元素,若是不是將刪除位置後的元素向左移numMoved個位置
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //將列表最後的元素置爲null,等待垃圾收集器收集
        elementData[--size] = null; // clear to let GC do its work
    }
removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
        //進行判斷,若是c爲null拋出異常
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            //遍歷數組,並檢查這個集合是否包含對應的值,移動要保留的值到數組前面,w最後值爲要保留的元素的數量
            //若保留,就將相同的元素移動到前段;不刪除,就將不一樣元素移動到前段
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // 確保異常拋出前的部分能夠完成指望的操做,而被遍歷的部分會被接到後面
            //r不等於size表示可能出錯了
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            //若是w等於size,表示所有元素都保留了,因此也就沒有刪除操做發生,因此會返回false;反之,返回true,並更改數組
            //而w不等於size的時候,即便try塊拋出異常,也能正確處理異常拋出前的操做,由於w始終爲要保留的前段部分的長度,數組也不會所以亂序
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }
clear() //清空ArrayList內的全部元素,不減少數組容量
public void clear() {
        //將集合修改次數加1  
        modCount++;
        //循環將列表中的全部元素置爲null,等待垃圾收集器收集
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        //將列表長度設爲0
        size = 0;
    }

查找元素

ArrayList提供了get(int index)用讀取ArrayList中的元素。因爲ArrayList是動態數組,因此咱們徹底能夠根據下標來獲取ArrayList中的元素,並且速度還比較快。

public E get(int index) {
        //判斷刪除位置是否正確,若是大於列表長度會拋出異常
        rangeCheck(index);
        //直接返回列表中下標等於index的元素
        return elementData(index);
    }

判斷元素是否存在列表中

ArrayList提供了contains(Object o)用於判斷元素是否存在於列表中。

注意:contains方法會遍歷ArrayList。

public boolean contains(Object o) {
        //調用indexOf方法判斷須要查找的元素在列表中的下標是否大於等於0,小於0則不存在
        return indexOf(o) >= 0;
    }
public int indexOf(Object o) {
        //由於ArrayList容許存在null,因此須要進行null判斷
        if (o == null) {
            //遍歷列表,若是列表存在null值的元素,直接返回其下標位置
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            //遍歷列表,使用equals判斷是否有相等的元素,有的話直接返回其下標位置
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        //列表中不能存在傳進來的元素,返回-1
        return -1;
    }

最小化ArrayList的實際存儲量

ArrayList提供了trimToSize()方法用於將底層數組的容量調整爲當前列表保存的實際元素的大小

public void trimToSize() {
        //將集合修改次數加1
        modCount++;
        //若是當前ArrayList的實際長度小於列表的長度,將列表超過size後的空餘的空間(包括null值)去除,調用Arrays.cppyof方法拷貝elementData,長度爲size
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

截取ArrayList部份內容

ArrayList提供了subList(int fromIndex, int toIndex)方法來實現部分數據的截取。

能夠從源碼中看到實際上是建立了一個SubList的內部對象,能夠理解爲是返回當前ArrayList的部分視圖,其實指向的存放數據的仍是一個地方。若是修改了subList返回的內容的話,原來的內容也會被修改。

public List<E> subList(int fromIndex, int toIndex) {
        //檢查須要截取的下標位置是否正確
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

其它方法

//判斷ArrayList是否爲空
    public boolean isEmpty() {
        return size == 0;
    }

    //反向查找元素位置,與上述的indexOf相反
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

    //將元素所有拷貝到v中
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

    //返回ArrayList拷貝後的Object數組
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    //返回ArrayList的模板數組。所謂模板數組,便可將T設置爲任意數據類型
    public <T> T[] toArray(T[] a) {
        //若a的長度小於ArrayList中的元素個數,返回拷貝了ArrayList中所有元素的新數組
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        //若a的長度大於等於ArrayList中的元素個數,則將ArrayList中的元素所有拷貝到a中
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

    //將ArrayList中的元素寫入到輸入流中,先寫容量,在寫元素
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    //從輸入流中讀取數據到elementData中,同樣是先讀容量,再讀數據
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            int capacity = calculateCapacity(elementData, size);
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

小結

  • ArrayList本身實現了序列化和反序列化,由於它實現了writeObject和readObject方法。
  • ArrayList基於數組實現,會自動擴容。
  • 添加元素時會本身判斷是否須要擴容,最好指定一個大概的大小,防止後面屢次擴容帶來的內存消耗;刪除元素時不會減小容量,刪除元素時,將刪除掉的位置元素置爲null,下次gc就會自動回收這些元素所佔的空間。
  • ArrayList是線程不安全的。
  • 使用iterator遍歷可能會引起多線程異常。

文章做者:leisurexi

新博客地址:https://leisurexi.github.io/

許可協議: 署名-非商業性使用-禁止演繹 4.0 國際 轉載請保留原文連接及做者。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息