1. 程式人生 > >第二章 ArrayList原始碼解析

第二章 ArrayList原始碼解析

一、對於ArrayList需要掌握的七點內容

  • ArrayList的建立:即構造器
  • 往ArrayList中新增物件:即add(E)方法
  • 獲取ArrayList中的單個物件:即get(int index)方法
  • 刪除ArrayList中的物件:即remove(E)方法
  • 遍歷ArrayList中的物件:即iterator,在實際中更常用的是增強型的for迴圈去做遍歷
  • 判斷物件是否存在於ArrayList中:contain(E)
  • ArrayList中物件的排序:主要取決於所採取的排序演算法(以後講)

二、原始碼分析

2.1、ArrayList的建立(常見的兩種方式)

        List<String> strList = new
ArrayList<String>(); List<String> strList2 = new ArrayList<String>(2);

ArrayList原始碼:

基本屬性:

    //物件陣列:ArrayList的底層資料結構
    private transient Object[] elementData;
    //elementData中已存放的元素的個數,注意:不是elementData的容量
    private int size;

注意:

  • transient關鍵字的作用:在採用Java預設的
    序列化機制的時候,被該關鍵字修飾的屬性不會被序列化。
  • ArrayList類實現了java.io.Serializable介面,即採用了Java預設的序列化機制
  • 上面的elementData屬性採用了transient來修飾,表明其不使用Java預設的序列化機制來例項化,但是該屬性是ArrayList的底層資料結構,在網路傳輸中一定需要將其序列化,之後使用的時候還需要反序列化,那不採用Java預設的序列化機制,那採用什麼呢?直接翻到原始碼的最下邊有兩個方法,發現ArrayList自己實現了序列化和反序列化的方法
        /**
         * Save the state of the <tt>ArrayList</tt> instance to a stream (that is,
         * serialize it).
         * 
         * 
    @serialData The length of the array backing the <tt>ArrayList</tt> * instance is emitted (int), followed by all of its elements * (each an <tt>Object</tt>) in the proper order. */ 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 array length s.writeInt(elementData.length); // 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(); } } /** * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, * deserialize it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in size, and any hidden stuff s.defaultReadObject(); // Read in array length and allocate array int arrayLength = s.readInt(); Object[] a = elementData = new Object[arrayLength]; // Read in all elements in the proper order. for (int i = 0; i < size; i++) a[i] = s.readObject(); }
    View Code

構造器:

    /**
     * 建立一個容量為initialCapacity的空的(size==0)物件陣列
     */
    public ArrayList(int initialCapacity) {
        super();//即父類protected AbstractList() {}
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity:" + initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    /**
     * 預設初始化一個容量為10的物件陣列
     */
    public ArrayList() {
        this(10);//即上邊的public ArrayList(int initialCapacity){}構造器
    }

在我們執行new ArrayList<String>()時,會呼叫上邊的無參構造器,創造一個容量為10的物件陣列。

在我們執行new ArrayList<String>(2)時,會呼叫上邊的public ArrayList(int initialCapacity),創造一個容量為2的物件陣列。

注意:

  • 上邊有參構造器的super()方法是ArrayList父類AbstractList的構造方法,這個構造方法如下,是一個空構造方法:
        protected AbstractList() {
        }
  • 在實際使用中,如果我們能對所需的ArrayList的大小進行判斷,有兩個好處:
    • 節省記憶體空間(eg.我們只需要放置兩個元素到陣列,new ArrayList<String>(2))
    • 避免陣列擴容(下邊會講)引起的效率下降(eg.我們只需要放置大約37個元素到陣列,new ArrayList<String>(40))

2.2、往ArrayList中新增物件(常見的兩個方法add(E)和addAll(Collection<? extends E> c))

2.2.1、add(E)

strList2.add("hello");

ArrayList原始碼:

    /**
     * 向elementData中新增元素
     */
    public boolean add(E e) {
        ensureCapacity(size + 1);//確保物件陣列elementData有足夠的容量,可以將新加入的元素e加進去
        elementData[size++] = e;//加入新元素e,size加1
        return true;
    }
    /**
     * 確保陣列的容量足夠存放新加入的元素,若不夠,要擴容
     */
    public void ensureCapacity(int minCapacity) {
        modCount++;
        int oldCapacity = elementData.length;//獲取陣列大小(即陣列的容量)
        //當陣列滿了,又有新元素加入的時候,執行擴容邏輯
        if (minCapacity > oldCapacity) {
            Object oldData[] = elementData;
            int newCapacity = (oldCapacity * 3) / 2 + 1;//新容量為舊容量的1.5倍+1
            if (newCapacity < minCapacity)//如果擴容後的新容量還是沒有傳入的所需的最小容量大或等於(主要發生在addAll(Collection<? extends E> c)中)
                newCapacity = minCapacity;//新容量設為最小容量
            elementData = Arrays.copyOf(elementData, newCapacity);//複製新容量
        }
    }

在上述程式碼的擴容結束後,呼叫了Arrays.copyOf(elementData, newCapacity)方法,這個方法中:對於我們這裡而言,先建立了一個新的容量為newCapacity的物件陣列,然後使用System.arraycopy()方法將舊的物件陣列複製到新的物件陣列中去了。

注意:

  • modCount變數用於在遍歷集合(iterator())時,檢測是否發生了add、remove操作。

2.2.2、addAll(Collection<? extends E> c)

使用方式:

        List<String> strList = new ArrayList<String>();
        strList.add("jigang");
        strList.add("nana");
        strList.add("nana2");
        
        List<String> strList2 = new ArrayList<String>(2);
        strList2.addAll(strList);

原始碼:

    /**
     * 將c全部加入elementData
     */
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();//將c集合轉化為物件陣列a
        int numNew = a.length;//獲取a物件陣列的容量
        ensureCapacity(size + numNew);//確保物件陣列elementData有足夠的容量,可以將新加入的a物件陣列加進去
        System.arraycopy(a, 0, elementData, size, numNew);//將物件陣列a拷貝到elementData中去
        size += numNew;//重新設定elementData中已加入的元素的個數
        return numNew != 0;//若加入的是空集合則返回false
    }

注意:

  • 從上述程式碼可以看出,若加入的c是空集合,則返回false
  • ensureCapacity(size + numNew);這個方法在上邊講
  • System.arraycopy()方法定義如下:
    public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos,  int length);

    將陣列src從下標為srcPos開始拷貝,一直拷貝length個元素到dest陣列中,在dest陣列中從destPos開始加入先的srcPos陣列元素。

除了以上兩種常用的add方法外,還有如下兩種:

2.2.3、add(int index, E element)

    /**
     * 在特定位置(只能是已有元素的陣列的特定位置)index插入元素E
     */
    public void add(int index, E element) {
        //檢查index是否在已有的陣列中
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size);
        ensureCapacity(size + 1);//確保物件陣列elementData有足夠的容量,可以將新加入的元素e加進去
        System.arraycopy(elementData, index, elementData, index+1, size-index);//將index及其後邊的所有的元素整塊後移,空出index位置
        elementData[index] = element;//插入元素
        size++;//已有陣列元素個數+1
    }

注意:

  • index<=size才行,並不是index<elementData.length

2.2.4、set(int index, E element)

    /**
     * 更換特定位置index上的元素為element,返回該位置上的舊值
     */
    public E set(int index, E element) {
        RangeCheck(index);//檢查索引範圍
        E oldValue = (E) elementData[index];//舊值
        elementData[index] = element;//該位置替換為新值
        return oldValue;//返回舊值
    }

2.3、獲取ArrayList中的單個物件(get(int index))

實現方式:

        ArrayList<String> strList2 = new ArrayList<String>(2);
        strList2.add("hello");
        strList2.add("nana");
        strList2.add("nana2");
        System.out.println(strList2.get(0));

原始碼:

    /**
     * 按照索引查詢物件E
     */
    public E get(int index) {
        RangeCheck(index);//檢查索引範圍
        return (E) elementData[index];//返回元素,並將Object轉型為E
    }
    /**
     * 檢查索引index是否超出size-1
     */
    private void RangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size);
    }

注:這裡對index進行了索引檢查,是為了將異常內容寫的詳細一些並且將檢查的內容縮小(index<0||index>=size,注意這裡的size是已儲存元素的個數);

事實上不檢查也可以,因為對於陣列而言,如果index不滿足要求(index<0||index>=length,注意這裡的length是陣列的容量),都會直接丟擲陣列越界異常,而假設陣列的length為10,當前的size是2,你去計算array[9],這時候得出是null,這也是上邊get為什麼減小檢查範圍的原因。

2.4、刪除ArrayList中的物件

2.4.1、remove(Object o)

使用方式:

strList2.remove("hello");

原始碼:

    /**
     * 從前向後移除第一個出現的元素o
     */
    public boolean remove(Object o) {
        if (o == null) {//移除物件陣列elementData中的第一個null
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {//移除物件陣列elementData中的第一個o
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * 刪除單個位置的元素,是ArrayList的私有方法
     */
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)//刪除的不是最後一個元素
            System.arraycopy(elementData, index + 1, elementData, index,numMoved);//刪除的元素到最後的元素整塊前移
        elementData[--size] = null; //將最後一個元素設為null,在下次gc的時候就會回收掉了
    }

 2.4.2、remove(int index)

使用方式:

strList2.remove(0);

原始碼:

    /**
     * 刪除指定索引index下的元素,返回被刪除的元素
     */
    public E remove(int index) {
        RangeCheck(index);//檢查索引範圍

        E oldValue = (E) elementData[index];//被刪除的元素
        fastRemove(index);
        return oldValue;
    }

注意:

  • remove(Object o)需要遍歷陣列,remove(int index)不需要,只需要判斷索引符合範圍即可,所以,通常:後者效率更高。

 2.5、判斷物件是否存在於ArrayList中(contains(E)

原始碼:

    /**
     * 判斷動態陣列是否包含元素o
     */
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    /**
     * 返回第一個出現的元素o的索引位置
     */
    public int indexOf(Object o) {
        if (o == null) {//返回第一個null的索引
            for (int i = 0; i < size; i++)
                if (elementData[i] == null)
                    return i;
        } else {//返回第一個o的索引
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;//若不包含,返回-1
    }

    /**
     * 返回最後一個出現的元素o的索引位置
     */
    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;
    }

注意:

  • indexOf(Object o)返回第一個出現的元素o的索引;lastIndexOf(Object o)返回最後一個o的索引

2.6、遍歷ArrayList中的物件(iterator())

使用方式:

        List<String> strList = new ArrayList<String>();
        strList.add("jigang");
        strList.add("nana");
        strList.add("nana2");
        
        Iterator<String> it = strList.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

原始碼:iterator()方法是在AbstractList中實現的,該方法返回AbstractList的一個內部類Itr物件

    public Iterator<E> iterator() {
        return new Itr();//返回一個內部類物件
    }

Itr:

    private class Itr implements Iterator<E> {
        
        int cursor = 0;//標記位:標記遍歷到哪一個元素
        int expectedModCount = modCount;//標記位:用於判斷是否在遍歷的過程中,是否發生了add、remove操作

        //檢測物件陣列是否還有元素
        public boolean hasNext() {
            return cursor != size();//如果cursor==size,說明已經遍歷完了,上一次遍歷的是最後一個元素
        }

        //獲取元素
        public E next() {
            checkForComodification();//檢測在遍歷的過程中,是否發生了add、remove操作
            try {
                E next = get(cursor++);
                return next;
            } catch (IndexOutOfBoundsException e) {//捕獲get(cursor++)方法的IndexOutOfBoundsException
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        //檢測在遍歷的過程中,是否發生了add、remove等操作
        final void checkForComodification() {
            if (modCount != expectedModCount)//發生了add、remove操作,這個我們可以檢視add等的原始碼,發現會出現modCount++
                throw new ConcurrentModificationException();
        }
    }

遍歷的整個流程結合"使用方式"與"Itr的註釋"來看。注:上述的Itr我去掉了一個此時用不到的方法和屬性。

三、總結

  • ArrayList基於陣列方式實現,無容量的限制(會擴容)
  • 新增元素時可能要擴容(所以最好預判一下),刪除元素時不會減少容量(若希望減少容量,trimToSize()),刪除元素時,將刪除掉的位置元素置為null,下次gc就會回收這些元素所佔的記憶體空間。
  • 執行緒不安全
  • add(int index, E element):新增元素到陣列中指定位置的時候,需要將該位置及其後邊所有的元素都整塊向後複製一位
  • get(int index):獲取指定位置上的元素時,可以通過索引直接獲取(O(1))
  • remove(Object o)需要遍歷陣列
  • remove(int index)不需要遍歷陣列,只需判斷index是否符合條件即可,效率比remove(Object o)高
  • contains(E)需要遍歷陣列

做以上總結,主要是為了與後邊的LinkedList作比較。

elementData

相關推薦

第二 ArrayList原始碼解析

一、對於ArrayList需要掌握的七點內容 ArrayList的建立:即構造器 往ArrayList中新增物件:即add(E)方法 獲取ArrayList中的單個物件:即get(int index)方法 刪除ArrayList中的物件:即remove(E)方法 遍歷ArrayList中的物件:

DelayQueue阻塞佇列第二原始碼解析

DelayQueue阻塞佇列系列文章 介紹 DelayQueue是java併發包中提供的延遲阻塞佇列,業務場景一般是下單後多長時間過期,定時執行程式等 1-DelayQueue的組成結構 /** * DelayQueue佇列繼承了AbstractQueue,

第二 ConcurrentHashMap原始碼解析

注:在看這篇文章之前,如果對HashMap的層不清楚的話,建議先去看看HashMap原始碼解析。 1、對於ConcurrentHashMap需要掌握以下幾點 Map的建立:ConcurrentHashMap() 往Map中新增鍵值對:即put(Object key, Object value)方

Java——ArrayList原始碼解析

以下針對JDK 1.8版本中的ArrayList進行分析。 概述     ArrayList基於List介面實現的大小可變的陣列。其實現了所有可選的List操作,並且元素允許為任意型別,包括null元素。除了實現List介面,此類還提供了操作內部用於儲存列表陣列大小

Java集合框架——jdk 1.8 ArrayList 原始碼解析 System.arraycopy 怎麼使用的?

前言:作為菜鳥,需要經常回頭鞏固一下基礎知識,今天看看 jdk 1.8 的原始碼,這裡記錄 ArrayList 的實現。 一、簡介   ArrayList 是有序的集合;   底層採用陣列實現對資料的增刪查改;   不是執行緒安全的;   有自動擴容的功能。 二、類圖  

Java原始碼解析系列(二)ArrayList原始碼解析

備註:以下都是基於JDK8 原始碼分析 ArrayList簡介        ArrayList 是一個數組佇列,相當於 動態陣列。與Java中的陣列相比,它的容量能動態增長。它繼承於AbstractList,實現了List, RandomAccess, Clonea

Java集合(一)-ArrayList原始碼解析

ArrayList是什麼? ArrayList是Java集合中的一份子,它的內部結構實為陣列並封裝了一些方法和特性方便使用者,為什麼不用陣列呢?因為ArrayList更加方便:如果你再不確定元素個數的情況下建立一個數組,那麼在陣列容量不夠的情況下需要手動擴容(也就是重新初始

ArrayList原始碼解析

概要 ArrayList 是一個動態陣列,它是執行緒不安全的,允許元素為null。 其底層資料結構依然是陣列,它實現了List, RandomAccess, Cloneable, java.io.Serializable介面,其中RandomAccess代表了其

資料結構-ArrayList原始碼解析

一、ArrayList簡介 1.1、ArrayList概述   1)ArrayList是個動態陣列,它是基於陣列實現的List類。   2)該類封裝了一個動態再分配的Object[]陣列,每一個類物件都有一個capacity屬性,表示它們所封裝的Object[]陣列的長度,當向ArrayList中新增元

第十一 AtomicInteger原始碼解析

1、原子類 可以實現一些原子操作 基於CAS 下面就以AtomicInteger為例。 2、AtomicInteger 在沒有AtomicInteger之前,對於一個Integer的執行緒安全操作,是需要使用同步鎖來實現的,當然現在也可以通過ReentrantLock來實現,但是最好最方

第三 CopyOnWriteArrayList原始碼解析

注:在看這篇文章之前,如果對ArrayList底層不清楚的話,建議先去看看ArrayList原始碼解析。 1、對於CopyOnWriteArrayList需要掌握以下幾點 建立:CopyOnWriteArrayList() 新增元素:即add(E)方法 獲取單個物件:即get(int)方法

第五 HashMap原始碼解析

5.1、對於HashMap需要掌握以下幾點 Map的建立:HashMap() 往Map中新增鍵值對:即put(Object key, Object value)方法 獲取Map中的單個物件:即get(Object key)方法 刪除Map中的物件:即remove(Object key)方法 判斷

Java集合(一):ArrayList原始碼解析

前言今天來介紹下ArrayList,在集合框架整體框架一章中,我們介紹了List介面,ArrayList繼承了AbstractList,實現了List。ArrayList在工作中經常用到,所以要弄懂這個類是極其重要的。構造圖如下:藍色線條:繼承綠色線條:介面實現正文Array

【JDk原始碼解析之一】ArrayList原始碼解析

1.ArrayList的繼承關係如圖所示: 2.巨集觀上說,ArrayList是基於動態陣列實現的,陣列具有按索引查詢的特性,所以訪問很快,適合經常查詢的資料。 3.具體原始碼解析。 為什麼說ArrayList是動態陣列,這個可以看它的建構函式。如下圖所示,有兩個構造方法

ArrayList原始碼解析[一]

歡迎轉載,轉載煩請註明出處,謝謝。 https://www.cnblogs.com/sx-wuyj/p/11177257.html 自己學習ArrayList原始碼的一些心得記錄.. 1.1 ArrayList的體系 Iterable : iterable接口裡定義了返回iterator的方法,相當於對

ArrayList原始碼解析(二)

歡迎轉載,轉載煩請註明出處,謝謝。 https://www.cnblogs.com/sx-wuyj/p/11177257.html 自己學習ArrayList原始碼的一些心得記錄. 繼續上一篇,ArrayList原始碼解析(一) addll(Collection<? extends E> c)

集合-ArrayList 原始碼解析

ArrayList是一種以陣列實現的List,與陣列相比,它具有動態擴充套件的能力,因此也可稱之為動態陣列。 類圖 ArrayList實現了List, RandomAccess, Cloneable, java.io.Serializable等介面。 ArrayList實現了List,提供了基礎的新增、刪除

《spring原始碼深度解析第二

1:容器基本用法 2:功能分析 3:工程搭建 4:spring的結構組成(beans包的層級結構、核心類介紹) 5:容器的基礎XMLBeanFactory(配置檔案封裝、載入

ArrayList實現原理以及原始碼解析(補充JDK1.7,1.8)

ArrayList實現原理以及原始碼解析(補充JDK1.7,1.8) ArrayList的基本知識在上一節已經討論過,這節主要看ArrayList在JDK1.6到1.8的一些實現變化。 JDK版本不一樣,ArrayList類的原始碼也不一樣。 1、ArrayList類結構:

ArrayList實現原理以及原始碼解析(JDK1.6)

ArrayList實現原理以及原始碼解析(JDK1.6) 1、ArrayList ArrayList是基於陣列實現的,是一個動態陣列,其容量能自動增長,類似於C語言中的動態申請記憶體,動態增長記憶體。 ArrayList不是執行緒安全的,只能用在單執行緒環境下。