JDK1.8源碼(一)——java.util.ArrayList


ArrayList 定義

ArrayList 是一個用數組實現的集合,支持隨機訪問,元素有序且可以重復。

1 public class ArrayList<E> extends AbstractList<E>
2         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
View Code

 

 藍色實線箭頭是指Class繼承關系

 綠色實線箭頭是指interface繼承關系

 綠色虛線箭頭是指接口實現關系

 由上可知ArrayList繼承AbstractList 並且實現了List和RandomAccess,Cloneable, Serializable接口。

、實現 List 接口

  List 接口定義了實現該接口的類都必須要實現的一組方法,如下所示,下面我們會對這一系列方法的實現做詳細介紹。

 

字段屬性

//集合默認大小
private static final int DEFAULT_CAPACITY = 10;
//空的數組實例
private static final Object[] EMPTY_ELEMENTDATA = {};
//這也是一個空的數組實例,和EMPTY_ELEMENTDATA空數組相比是用於了解添加元素時數組膨脹多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存儲 ArrayList集合的元素,集合的長度即這個數組的長度
//1、當 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 時將會清空 ArrayList
//2、當添加第一個元素時,elementData 長度會擴展為 DEFAULT_CAPACITY=10
//3、transient修飾表示序列化對象的時候,這個屬性就不會序列化到指定的目的地中;通過方法來手動序列化,下面講解
transient Object[] elementData; 
//表示集合的長度
private int size;
View Code

 

構造函數

①、無參構造函數

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

  此無參構造函數將創建一個 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 聲明的數組,注意此時初始容量是0,而不是大家以為的 10。

  注意:根據默認構造函數創建的集合,ArrayList list = new ArrayList();此時集合長度是0.

②、給定大小有參構造函數

 1     public ArrayList(int initialCapacity) {
 2         if (initialCapacity > 0) {
 3             this.elementData = new Object[initialCapacity];
 4         } else if (initialCapacity == 0) {
 5             this.elementData = EMPTY_ELEMENTDATA;
 6         } else {
 7             throw new IllegalArgumentException("Illegal Capacity: "+
 8                                                initialCapacity);
 9         }
10     }
View Code

  初始化集合大小創建 ArrayList 集合。當大於0時,給定多少那就創建多大的數組並賦給賦給elementData;當等於0時,將空的數組實例EMPTY_ELEMENTDATA 賦給elementData;當小於0時,拋出異常。

③、泛型參數有參構造函數

public ArrayList(Collection<? extends E> c) {
    //將collection對象轉換成數組,然后將數組的地址的賦給elementData。
    elementData = c.toArray();
    //更新size的值,並且如果size大於0
    if ((size = elementData.length) != 0) {
        if (elementData.getClass() != Object[].class)
            //把collection對象的內容copy到elementData中
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        //size等於0直接將空對象EMPTY_ELEMENTDATA的地址賦給elementData
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
View Code

Collection是最基本的集合接口,一個Collection代表一組Object,即Collection的元素。?是“任意類”的意思,extends繼承不多說,E是指定類型。

  用法舉例:

List stu=new ArrayList<Student>();
stu.add(new Student());
List students=new ArrayList<Student>(stu);
View Code

 

添加元素

public boolean add(E e) {
    //添加元素之前,首先要確定集合的大小,如果集合不夠裝則需要擴容
    //第一次size=0
    ensureCapacityInternal(size + 1);
    //集合size位置賦值,先引用size的值,再對size自增
    elementData[size++] = e;
    return true;
}
View Code
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //第一次添加,空集合,10和1取最大值,所以第一次添加元素初始集合length為10
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //擴容判斷
    ensureExplicitCapacity(minCapacity);
}
View Code
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    //如果size+1大於集合的大小,說明剛好集合已經滿了,則需要擴容
    //size 是集合實際元素個數,elementData.length是集合長度,元素沒滿,用null填滿
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
View Code
private void grow(int minCapacity) {
    //集合的容量
    int oldCapacity = elementData.length;
    //>>1表示右移位,相當於除以2的n次方,這里除以2的1次方,相當於容量增加原來容量的一半
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        //擴容一半后還比minCapacity小,那就只有oldCapacity=0的情況了,也就是第一次添加元素,集合大小為10
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        //擴容容量大於最大集合大小
        newCapacity = hugeCapacity(minCapacity);
        //數組的復制
        elementData = Arrays.copyOf(elementData, newCapacity);
}
View Code
private static int hugeCapacity(int minCapacity) {
    //Integet.MAX_VALUE+1時就會溢出,這時會變成負數,Integer.MAX_VALUE+1=Integer.MIN_VALUE
    if (minCapacity < 0)
        throw new OutOfMemoryError();
    //size+1比最大集合大小還大就取最大整數,否則取最大集合大小
    //所以先擴容到MAX_ARRAY_SIZE,下一次擴容就擴容到Integer.MAX_VALUE 
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}
View Code
public static <T> T[] copyOf(T[] original, int newLength) {
    return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    // 判斷類型是否為Object,是Object,生成一個大小為newLength類型為Object的數組實例
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);// 類型不為Object,通過反射重新生成一個大小為newLength的新類型數組實例
    ///將原數組內容拷貝到新數組中,新數組取最小的數組長度,這里指copy的數組的長度,是原數組的長度
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}
//native關鍵字表示系統方法,Java可以通過JNI來調用其他語言(主要還是C/C++語言)編寫的方法
/*Object src : 原數組
int srcPos : 從元數據的起始位置開始
Object dest: 目標數組
int destPos: 目標數組的開始起始位置
int length : 要copy的數組的長度
*/
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);
View Code

  ①、當通過 ArrayList() 構造一個空集合,初始長度是為0的,size也為0,第 1 次添加元素,會創建一個長度為10的數組,並將該元素賦值到數組的第一個位置,下標為0的位置。

  ②、第 2 次添加元素,集合不為空,而且由於集合的長度size+1是小於數組的長度10,所以直接添加元素到數組的第二個位置,不用擴容。

  ③、第 11 次添加元素,此時 size+1 = 11,而數組長度是10,這時候創建一個長度為10+10*0.5 = 15 的數組(擴容1.5倍),然后將原數組元素引用拷貝到新數組。並將第 11 次添加的元素賦值到新數組下標為10的位置。

  ④、當第一次擴容容量 oldCapacity + (oldCapacity >> 1)大於最大集合大小Integer.MAX_VALUE - 8時,判斷size+1和Integer.MAX_VALUE - 8的大小,第一次size+1肯定小,這時擴容到Integer.MAX_VALUE - 8

  ⑤、當集合size=Integer.MAX_VALUE - 8時,然后添加元素時,創建一個大小為 Integer.MAX_VALUE 的數組,在進行元素添加。

  ⑥、第 Integer.MAX_VALUE + 1 次添加元素時,拋出 OutOfMemoryError 異常。

  注意:能向集合中添加 null 的,因為數組可以有 null 值存在。

 

查找元素

public E get(int index) {
    //檢查索引是否越界,越界則拋出異常
    rangeCheck(index);
    //直接返回處於該下標位置的數組元素
    return elementData(index);
}
View Code

 

修改元素

public E set(int index, E element) {
    //判斷索引是否越界
    rangeCheck(index);
    //獲取原數組指定索引的元素
    E oldValue = elementData(index);
    //將指定索引處的元素替換為 element
    elementData[index] = element;
    return oldValue;//返回原數組索引元素
}
View Code

 

刪除元素

①、通過索引位置刪除

public E remove(int index) {
    rangeCheck(index);//判斷給定索引的范圍,超過集合大小則拋出異常
    modCount++;
    E oldValue = elementData(index);//得到索引處的刪除元素
    int numMoved = size - index - 1;
    if (numMoved > 0)//size-index-1 > 0 索引處不是最后一個元素
        //將數組elementData 的下標index+1之后長度為 numMoved的元素拷貝到從index開始的位置
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; //將數組最后一個元素置為 null
    //返回刪除的元素
    return oldValue;
}
View Code

、直接刪除指定元素

public boolean remove(Object o) {
    if (o == null) {//如果刪除的元素為null
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                //return第一表示返回值,第二表示中止函數往下執行
                return true;
            }
    } else {//不為null,通過equals方法判斷對象是否相等
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
//和通過索引刪除元素方法相同
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; // clear to let GC do its work
}
View Code

remove(Object o)方法是刪除第一次出現的該元素。然后通過System.arraycopy進行數組自身拷貝。

注意並不是刪除所有該元素

 

遍歷集合

  ①、迭代器 iterator

  用法:

ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
Iterator<String> it = list.iterator();
while(it.hasNext()){
    String str = it.next();
    System.out.print(str+" ");
}
View Code

interator方法返回一個 Itr 對象,這個類是 ArrayList 的內部類。

public Iterator<E> iterator() {
    return new Itr();
}
View Code
private class Itr implements Iterator<E> {
    int cursor; //游標,下一個要返回的元素的索引
    int lastRet = -1; //上一次返回元素的索引; 如果沒有這樣的話返回-1.
    int expectedModCount = modCount;//這個值是在用戶調用ArrayList的iterator方法時候確定的

    public boolean hasNext() {
        //通過 cursor != size 判斷是否還有下一個元素
        //下面每次獲取一個元素cursor都要+1
        return cursor != size;
    }

    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];//返回索引為i處的元素,並將 lastRet賦值為i
    }

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

        try {
            ArrayList.this.remove(lastRet);//調用ArrayList的remove方法刪除元素
            cursor = lastRet;//游標指向刪除元素的位置,本來是lastRet+1的,這里刪除一個元素,然后游標就不變了
            lastRet = -1;//lastRet恢復默認值-1,因為上一個返回的元素已經刪除了
            expectedModCount = modCount;//expectedModCount值和modCount同步,因為進行add和remove操作,modCount會加1
        } 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();
    }
    //expectedModCount這個值是在用戶調用ArrayList的iterator方法時候確定的
    //前面在新增元素add() 和 刪除元素 remove() 時,我們可以看到 modCount++。修改set() 是沒有的
    //也就是說不能在迭代器進行元素迭代時進行增加和刪除操作,否則拋出異常
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}
View Code

邏輯上講,迭代時可以添加元素,但是一旦開放這個功能,很有可能造成很多意想不到的情況。
比如你在迭代一個ArrayList,迭代器的工作方式是依次返回給你第0個元素,第1個元素,等等,假設當你迭代到第5個元素的時候,你突然在ArrayList的頭部插入了一個元素,使得你所有的元素都往后移動,於是你當前訪問的第5個元素就會被重復訪問。
java認為在迭代過程中,容器應當保持不變。因此,java容器中通常保留了一個域稱為modCount,每次你對容器修改,這個值就會加1。當你調用iterator方法時,返回的迭代器會記住當前的modCount,隨后迭代過程中會檢查這個值,一旦發現這個值發生變化,就說明你對容器做了修改,就會拋異常。  

 迭代時不能調用 ArrayList.remove() 方法,可以調用 迭代器的 remove() 方法刪除元素:

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> it = list.iterator();
while(it.hasNext()){
    String str = it.next();
    System.out.print(str+" ");
    list.remove(str);//集合遍歷時進行刪除或者新增操作,都會拋出 ConcurrentModificationException 異常
    //list.add(str);
    list.set(0, str);//修改操作不會造成異常
    it.remove();//調用迭代器內部刪除方法
}
View Code

 

 

序列化

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    int expectedModCount = modCount;
    s.defaultWriteObject();
    s.writeInt(size);
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}
View Code
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;
    s.defaultReadObject();
    s.readInt(); 
    if (size > 0) {
        ensureCapacityInternal(size);
        Object[] a = elementData;
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}
View Code

 ArrayList在序列化的時候會調用writeObject,直接將size和element寫入ObjectOutputStream;反序列化時調用readObject,從ObjectInputStream獲取size和element,再恢復到elementData。假如elementData的長度為10,而其中只有5個元素,那么在序列化的時候只需要存儲5個元素,而數組中后面5個元素是不需要存儲的。於是將elementData定義為transient,避免了Java自帶的序列化機制,就可以保證只序列化實際存儲的那些元素,而不是整個數組,從而節省空間和時間。

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM