Java容器源碼攻堅戰--第二戰:ArrayList

基於java10.1java

零、前言

若是想讀讀源碼測試功力,或讀讀源碼修養身心,或讀讀源碼知足自虐傾向,我建議第一個類是:ArrayList
第1、經常使用----因此用法比較熟悉,看完源碼你也會更明白如何去用
第2、相對簡單----1595行代碼,刨去註釋的一大堆也沒有太多,仍是hold的住的git

總得來講ArrayList源碼最主要的是對System.arraycopy的理解,不少操做都是基於此
void arraycopy( Object src, //源數組
                    int  srcPos,//源數組中的起始位置
                    Object dest,//目標數組
                    int destPos,//目的地數據中的起始位置
                    int length);//要複製的數組元素的數量

一.繼承與實現:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
9414344-f03aa2cd5077146a
image

2、成員變量

//serialVersionUID來驗證版本一致性
    private static final long serialVersionUID = 8683452581122892189L;
    
    //默認列表的容量
    private static final int DEFAULT_CAPACITY = 10;
    
    //一個空的Object數組--空數組(本文中別名:空1)
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    //另外一個空的Object數組--默認容量空數組(本文中別名:空2)
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

     //列表中盛放的數組
    transient Object[] elementData; //非私有來簡化內部類訪問
    
    //內部元素個數
    private int size;
    
    //最大數組尺寸,這裏是Integer最大值-8
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

二.構造方法

1.構造方法1:無參構造

使用無參構造ArrayList,在未添加元素前,內部數組的長度是爲0。在添加第一個元素時,纔會擴容到10github

//無參構造:elementData=空2
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
2.構造方法2:一參構造:指定初始容量
//入參initialCapacity:初始容量
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {//入參>0有效
            //建立長度爲入參的Object數組
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {//入參<0異常
            throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
        }
    }
3.構造方法3:用已有Collection對象,建立ArrayList
// 入參c:已有的容器對象
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();//傳入的集合轉化爲數組並賦值給elementData
        //獲取數組長度賦值值給size,當size不爲0
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                //Arrays.copyOf見:A--01
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {//入參 = 0 時:elementData=空1
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
A--01:java.util.Arrays#copyOf(U[], int, java.lang.Class<? extends T[]>)

將一個數組拷貝指定長度,變爲另外一種類型的數組編程

@param <U> 初始數組類型
@param <T> 返回數組類型
@param original 被拷貝數組
@param newLength 拷貝的長度
@param newType 返回的數組類型
@return 
     
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {

    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.添加: 代號m1
m1 : java.util.ArrayList#add(E)

modCount:父類AbstractList中的protected變量,每次添加和移除都會+1,記錄全部的修改次數
做用:在Iterator使用時校驗指望修改的次數與真實修改次數是否相同數組

*@param e 添加的元素
    public boolean add(E e) {
        modCount++;//列表在結構上修改的次數
        add(e, elementData, size);//此處調用三參的添加方法:見:m1-1
        return true;//返回是否添加成功
    }
m1-1 : java.util.ArrayList#add(E, java.lang.Object[], int)
*@param e 添加的元素
*@param elementData 數組
*@param s 索引位置
    private void add(E e, Object[] elementData, int s) {
        //要添加的索引爲等於數組的長度時進行擴容操做
        if (s == elementData.length)
            elementData = grow();//grow()返回一個擴容後的數組:見:m-1-1
        //將傳入的元素放到數組的最後一位
        elementData[s] = e;(注:因爲數組標號從零開始,新數組長度s+1,最後一個元素下標號爲s)
        size = s + 1;//將成員變量size+1
    }
m-1-1 : java.util.ArrayList#grow()

擴容操做微信

private Object[] grow() {
        return grow(size + 1);//調用了一參的grow()方法
    }
    
    *@param minCapacity 最小容量
    private Object[] grow(int minCapacity) {
        //newCapacity(minCapacity)擴容核心操做:見m-1-1-1
        return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity));
    }
m-1-1-1:java.util.ArrayList#newCapacity

擴容核心操做: 最小容量size+1,舊容量:數組長,新容量:1.5倍舊容量
拿教室舉例:minCapacity就是班級學生數量+1,oldCapacity就是擴建前的座位數,newCapacity就是擴建後的座位數dom

*@param minCapacity 最小容量
    private int newCapacity(int minCapacity) {
        //oldCapacity:舊容量  newCapacity:1.5倍舊容量
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//即+50%
       
        if (newCapacity - minCapacity <= 0) { //新容量小於等於最小容量
            //若是elementData=空2,擴容到DEFAULT_CAPACITY(即10),這就是分空1和空2的目的
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
         //新容量大於最小容量時, newCapacity沒達到最大尺寸返回newCapacity,不然hugeCapacity(minCapacity)
        return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity);
    }
    
    //minCapacity大於數組最大尺寸,返回整型的最大值,不然返回數組最大尺寸
     private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE)? Integer.MAX_VALUE: MAX_ARRAY_SIZE;
    }

2.定位添加:代號:m2
m2 : java.util.ArrayList#add(int, E)
* @param 插入的索引位置
     * @param 插入的元素
    public void add(int index, E element) {
        rangeCheckForAdd(index);//m2-1:檢查索引合法性
        modCount++;
        final int s;//局部變量,記錄方法執行前size值
        Object[] elementData//局部變量,記錄方法執行前elementData數組
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow();//擴容1.5倍
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);//m2-2:核心方法
        elementData[index] = element;
        size = s + 1;
    }
m2-1 :java.util.ArrayList#rangeCheckForAdd
private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)//索引在(0,size)以外報異常
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
m2-2 :java.util.ArrayList#rangeCheckForAdd
//將[源數組src]從[指定位置srcPos]選取連續[長度length]個元素複製到[目標數組dest]的[指定位置destPos]。
    public static native 
    void arraycopy( Object src, //源數組
                    int  srcPos,//源數組中的起始位置
                    Object dest,//目標數組
                    int destPos,//目的地數據中的起始位置
                    int length);//要複製的數組元素的數量

9414344-24966c392d35456d.png
arraycopy.png
3.添加全部:代號m3
* @param 已有的容器對象
     * @return 列表是否已被修改
     */
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();//將容器對象轉化爲數組a
        modCount++;//修改次數+1
        int numNew = a.length;//臨時變量numNew記錄a的長度
        if (numNew == 0)//a的長度爲0
            return false;//說明列表未被修改
        Object[] elementData;//臨時變量elementData:數組
        final int s;//臨時變量s:數組長度
        //加入的元素大於數組容積和當前元素個數之差,也就是放入這些元素後會佔滿
        if (numNew > (elementData = this.elementData).length - (s = size))
            //擴容
            elementData = grow(s + numNew);
        //將a數組,從0開始,拷貝numNew個元素到elementData數組的s索引處
        System.arraycopy(a, 0, elementData, s, numNew);
        size = s + numNew;//維護size
        return true;////說明列表被修改
    }
4.定點添加全部:代號m4
public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);//見:m2-1
        Object[] a = c.toArray();
        modCount++;
        int numNew = a.length;
        if (numNew == 0)
            return false;
        Object[] elementData;
        final int s;
        if (numNew > (elementData = this.elementData).length - (s = size))
            elementData = grow(s + numNew);//至此同上m3
        int numMoved = s - index;//添加的位置以後的元素個數
        if (numMoved > 0)//說明index在元素個數以內
        //將elementData數組,從index開始,拷貝numMoved個元素到elementData數組的index + numNew索引處
        //至關於挪一下索引位後的元素
            System.arraycopy(elementData, index,
                             elementData, index + numNew,
                             numMoved);
        //將a數組,從0開始,拷貝numNew個元素到elementData數組的index索引處
        System.arraycopy(a, 0, elementData, index, numNew);
        size = s + numNew;
        return true;
    }

5.根據索引刪除:代號m5
m5 : java.util.ArrayList#remove(int)
public E remove(int index) {
        Objects.checkIndex(index, size);//檢查索引合法性
        final Object[] es = elementData;//將集合賦予臨時變量es

        E oldValue = (E) es[index];//臨時變量記錄刪除值
        fastRemove(es, index);//m5-1:刪除的核心方法
        return oldValue;//返回刪除的值
    }
m5-1 : java.util.ArrayList#fastRemove
/**
     * 私有的移除方法:跳過邊界檢查而且不反會移除的值
     */
    private void fastRemove(Object[] es, int i) {
        modCount++;//修改次數+1
        final int newSize;//新尺寸:元素總量-1
        if ((newSize = size - 1) > i)//索引在元素總量-1以內
            //將es數組,從i + 1開始,拷貝size - (i+1)個元素到es數組的i索引處
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }
6.移除指定元素
public boolean remove(Object o) {
        final Object[] es = elementData;
        final int size = this.size;
        int i = 0;
        found: {
            if (o == null) {
                for (; i < size; i++)
                    if (es[i] == null)
                        break found;
            } else {
                for (; i < size; i++)
                    if (o.equals(es[i]))
                        break found;
            }
            return false;
        }
        //尋找到第一次出現的指定元素索引位置
        fastRemove(es, i);//m5-1:刪除的核心方法
        return true;
    }
7.移除全部指定元素:代號m7
public boolean removeAll(Collection<?> c) {
        return batchRemove(c, false, 0, size);//batchRemove見:m7-1
    }
m7-1:java.util.ArrayList#batchRemove
boolean batchRemove(Collection<?> c, boolean complement,
                        final int from, final int end) {
        Objects.requireNonNull(c);
        final Object[] es = elementData;//es變量記錄當前數組
        int r;
        // Optimize for initial run of survivors
        for (r = from;; r++) {
            if (r == end)//沒有找到該元素
                return false;
            if (c.contains(es[r]) != complement)//包含但尚未所有掃描
                break;
        }
        int w = r++;
        try {
            for (Object e; r < end; r++)
                if (c.contains(e = es[r]) == complement)
                    es[w++] = e;
        } catch (Throwable ex) {
            System.arraycopy(es, r, es, w, end - r);
            w += end - r;
            throw ex;
        } finally {
            modCount += end - w;
            //shiftTailOverGap:見m7-1-1
            shiftTailOverGap(es, w, end);
        }
        return true;
    }
7-1-1.範圍移除
private void shiftTailOverGap(Object[] es, int lo, int hi) {
        //將es數組,從hi開始,拷貝size - hi個元素到es數組的lo索引處
        System.arraycopy(es, hi, es, lo, size - hi);
        for (int to = size, i = (size -= hi - lo); i < to; i++)
            es[i] = null;//將移除的元素置空
    }
8.獲取元素:代號m8
m8 : java.util.ArrayList#get
/**
     * 返回列表中指定位置的元素
     */
    public E get(int index) {
        //m8-1:檢查索引是否合法(1.9開始有)
        Objects.checkIndex(index, size);
        return elementData(index);//m8-2
    }
m8-1 : java.util.Objects#checkIndex
public static
    int checkIndex(int index, int length) {
        return Preconditions.checkIndex(index, length, null);//m8-1-1
    }
m8-1-1 : jdk.internal.util.Preconditions#checkIndex
//判斷索引值是否在(0,length)
    public static <X extends RuntimeException>
    int checkIndex(int index, int length,
                   BiFunction<String, List<Integer>, X> oobef) {
        if (index < 0 || index >= length)
            throw outOfBoundsCheckIndex(oobef, index, length);//這就是常常遇到的角標越界
        return index;
    }
m8-2 : java.util.ArrayList#elementData
E elementData(int index) {
        return (E) elementData[index];//返回數組在索引上的值
    }

9.修改元素
public E set(int index, E element) {
        //檢查索引是否越界
        Objects.checkIndex(index, size);
        //記錄舊值
        E oldValue = elementData(index);
        //設置新值
        elementData[index] = element;
        //返回舊值
        return oldValue;
    }

4、其餘方法

1.獲取集合大小:java.util.ArrayList#size
public int size() {
        return size;
    }
2.是否非空:java.util.ArrayList#isEmpty
public boolean isEmpty() {
        return size == 0;
    }
3.清空:java.util.ArrayList#clear
public void clear() {
        modCount++;//操做數+1
        final Object[] es = elementData;//es存儲當前數組
        for (int to = size, i = size = 0; i < to; i++)
            es[i] = null;//所有置空
    }
4.元素出現的索引位
indexOf(el) 元素第一次出現的索引位置
public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;//從頭開始找,直到相等時,返回i
        }
        return -1;//找不到返回-1
    }
lastIndexOf(el) 元素最後一次出現的索引位置
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;//從後往前找,直到相等時,返回i
        }
        return -1;//找不到返回-1
    }
5.是否包含某元素
public boolean contains(Object o) {
        return indexOf(o) >= 0;//找到有索引就ok
    }
4.轉換爲數組:java.util.ArrayList#toArray(T[])
public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
public <T> T[] toArray(T[] a) {
        if (a.length < size)
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

5、迭代器:

1.Iterator上一篇講的挺詳細:見--Java容器源碼攻堅戰--第一戰:Iterator

2.這裏主要講ArrayList特有的私人迭代器:ListIterator

能夠認爲是Iterator的升級版,繼承自Iterator,並且多了一些操做函數

ListIterator
public interface ListIterator<E> extends Iterator<E> {
    
    boolean hasNext();//是否有下一元素
    E next();//下一元素
    boolean hasPrevious();//是否有前一元素
    E previous();//前一元素
    int nextIndex();//下一個元素索引
    int previousIndex();//前一個元素索引

    void remove();//移除
    void set(E e);//設置
    void add(E e);//添加
}
public ListIterator<E> listIterator() {
      return new ListItr(0);
  }
  
public ListIterator<E> listIterator(int index) {
    rangeCheckForAdd(index);
    return new ListItr(index);
}
ListIterator實現類
private class ListItr extends Itr implements ListIterator<E> {
    ListItr(int index) {//構造函數傳入索引值
        super();
        cursor = index;//遊標處於索引位
    }
    public boolean hasPrevious() {
        return cursor != 0;//遊標不爲0說明有前元素
    }
    public int nextIndex() {
        return cursor;
    }
    public int previousIndex() {
        return cursor - 1;
    }
    @SuppressWarnings("unchecked")
    public E previous() {
        checkForComodification();
        //索引爲遊標-1
        int i = cursor - 1;
        if (i < 0)
            throw new NoSuchElementException();
        //elementData儲存當前數組
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i;//遊標-1
        return (E) elementData[lastRet = i];//返回元素
    }
    public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();
        try {
            //設置最後返回的那個元素索引
            ArrayList.this.set(lastRet, e);
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
    public void add(E e) {
        checkForComodification();
        try {
            int i = cursor;
            //在最後返回的那個元素索引處添加元素
            ArrayList.this.add(i, e);
            cursor = i + 1;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

就醬紫,ArrayList還有個SubList沒有接觸過,暫時就不看了源碼分析


後記:捷文規範

1.本文成長記錄及勘誤表
項目源碼 日期 備註
V0.1--無 2018-10-2 Java容器源碼攻堅戰--第二戰:ArrayList
V0.2--無 - -
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
個人github 個人簡書 個人CSDN 我的網站
3.聲明

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3---我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持測試

相關文章
相關標籤/搜索