1. 程式人生 > >閱讀ArrayList原始碼的一些記錄

閱讀ArrayList原始碼的一些記錄

ArrayList的底層是基於陣列實現的,但是我們知道陣列的長度一旦確定就不能夠再次變化,ArrayList的長度是可以變化的,其實就是在需要擴容的時候,重新生成一個數組,並把原陣列中的元素放到新的陣列中,用新的陣列替代就得陣列,就完成了ArrayList的擴容。

本文是基於JDK1.8的原始碼,同時也會提到一些和JDK1.6的一些差別

一、構造方法

1、給定初始大小的構造方法

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {// 如果大於0,就按照給定的大小來初始化陣列
        this.elementData =
new Object[initialCapacity]; } else if (initialCapacity == 0) {// 如果等於0,則初始化為一個空陣列 this.elementData = EMPTY_ELEMENTDATA; } else {// 如果小於0,則直接丟擲異常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }

2、無參構造方法

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

此處的無參構造方法是初始化一個空的陣列,相比於1.6來說,無參構造方法有一點變化,在1.6中,如果呼叫無參構造方法,會把elementData陣列的長度初始化為10

3、以傳入的Collection為基礎構建ArrayList

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; } }

這個構造方法沒什麼說的,就是把傳入的集合轉化為陣列,然後放到elementData中

下面按照增刪改查依次說明

二、增

1、在陣列尾部插入

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 確保長度夠用,否則就擴容
    elementData[size++] = e;// 在陣列尾部新增元素
    return true;
}
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//用無參構造方法生成物件的時候,elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);// 取其中比較大的值
    }
    ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // 是否需要擴容,如果需要就呼叫grow方法
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;// 原來的容量
    // 正常的擴容原則 原長度 + 原長度的一半(只取整數部分)(eg:1、原長度是10則正常擴容長度為10 + (10 >> 1)=15   2.原長度為11,則正常擴容以後的長度為11 + (11 >> 1) = 16)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果按照正常的擴容演算法擴容後的長度還不能達到要求,則按照傳進來的長度進行擴容
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)// 這種情況一般不會發生,除非你往List中添加了很多很多資料
        newCapacity = hugeCapacity(minCapacity);
    // 把原來的資料放到新的陣列中
    elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // 如果小於0就表示溢位了
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

JDK1.6的擴容原則和這個有一定的差別,沒有那麼複雜,可以看一下1.6的擴容原則

public void ensureCapacity(int minCapacity) {
	modCount++;
	int oldCapacity = elementData.length;// 原長度
	if (minCapacity > oldCapacity) {// 是否需要擴容
	    Object oldData[] = elementData;
        // 正常的擴容演算法,原長度的3/2 + 1
	    int newCapacity = (oldCapacity * 3)/2 + 1;
    	if (newCapacity < minCapacity)// 不滿足要求,則把傳入的長度作為擴容後的長度
		    newCapacity = minCapacity;
        elementData = Arrays.copyOf(elementData, newCapacity);
	}
}

2、在指定位置插入

public void add(int index, E element) {
    rangeCheckForAdd(index);// 檢測index是否合法(0 < index < size)
    ensureCapacityInternal(size + 1);  // 是否要擴容
    // 把index以及其後的元素往後移動,由此可以看出在特定位置插入資料的效率並不高
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}

3、把一個集合的元素插入到陣列的尾部

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();// 把集合轉為陣列
    int numNew = a.length;
    ensureCapacityInternal(size + numNew); // 是否需要擴容
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

4、把一個集合插入到陣列的特定位置

和插入一個元素的套路一樣,此處只貼一下程式碼

public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    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 != 0;
}

三、刪

1、按照索引刪除

public E remove(int index) {
    rangeCheck(index);// 檢查index是否大於等於size,如果是會丟擲異常
    modCount++;
    E oldValue = elementData(index);// 獲取指定索引的上的值
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 把index後面的元素前移,所以移除的效率不高
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}

2、按照值來刪除

如果有此值,刪除成功返回true,否則返回false

// 此方法是從頭開始迴圈查詢,找到以後就刪除並返回,後面相同的值不會被刪除
public boolean remove(Object o) {
    // 分是否為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;
}
// 方法相比於remove(int)來說,減少了index的合法性判斷,以及舊值的獲取
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
}

3、清空

// 迴圈把陣列的每個元素都置為null
public void clear() {
    modCount++;
    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;
    size = 0;
}

四、改

public E set(int index, E element) {
    // 校驗index的合法性
    rangeCheck(index);
    // 獲取舊值,返回時用
    E oldValue = elementData(index);
    // 修改值
    elementData[index] = element;
    return oldValue;
}

五、查

public E get(int index) {
    rangeCheck(index);// 校驗index的合法性
    return elementData(index);// 獲取指定索引上的值
}

六、其他的一些方法

1、獲取List的長度

因為ArrayList維護了一個size成員變數來表示其長度,直接獲取size的長度即可

public int size() {
    return size;
}

2、集合是否為空集合

長度為空就是空集合

public boolean isEmpty() {
    return size == 0;
}

3、是否包含指定的元素

遍歷去比較

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}
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;
    }
    return -1;
}

七、ArrayList的遍歷

以下都是個人的測試程式碼 三種遍歷方式

public void testArrayList(){
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    // 第一種方法
    for(int i = 0; i < list.size(); i ++){
        System.out.println(list.get(i));
    }
    // 第二種
    for(String str : list){
        System.out.println(str);
    }
    // 第三種,迭代器
    Iterator<String> iterator = list.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }
}

下面我們看一種情形,就是刪除List中的所有與指定值相同的值 第一種(並不能達到我們預想的結果)

public void testFor(){
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("a");
    list.add("b");
    list.add("c");
    list.add("d");
    list.add("a");
    list.add("a");
    list.add("a");
    for(int i = 0; i < list.size(); i ++){
        if("a".equals(list.get(i))){
            list.remove(i);
        }
    }
    // 經過刪除以後,list中的元素為a,b,c,d,a顯然沒有達到我們要刪除所有的a的目的
    for(int i = 0; i < list.size(); i ++){
        System.out.println(list.get(i));
    }
}

第二種(迭代器方法 推薦使用)

public void testiter(){
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("a");
    list.add("b");
    list.add("c");
    list.add("d");
    list.add("a");
    list.add("a");
    list.add("a");
    Iterator<String> iterator2 = list.iterator();
    while(iterator2.hasNext()){
        String val = iterator2.next();
        if("a".equals(val)){
            iterator2.remove();
        }
    }
    // 經過刪除後 list 中的元素為b,c,d 得到了我們所期望的結果
    for(int i = 0; i < list.size(); i ++){
        System.out.println(list.get(i));
    }
}

第三種方法

public void testFor2(){
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("a");
    list.add("b");
    list.add("c");
    list.add("d");
    list.add("a");
    list.add("a");
    list.add("a");
    for(int i = 0; i < list.size(); i ++){
        if("a".equals(list.get(i))){
            list.remove(i);
            i --;
        }
    }
    // 經過刪除以後,list中的元素為b,c,d 同樣達到了我們的要求,不過不建議用這種方法,推薦用迭代器去刪除
    for(int i = 0; i < list.size(); i ++){
        System.out.println(list.get(i));
    }
}

由於比較好奇foreach的實現就寫了兩個測試方法,程式碼如下

public void testForEach(){
    List<String> list = new ArrayList<>();
    for(String str : list){
        System.out.println(str);
    }
}
public void testIter(){
    List<String> list = new ArrayList<>();
    Iterator<String> iterator = list.iterator();
    while(iterator.hasNext()){
        String s = iterator.next();
        System.out.println(s);
    }
}

通過idea反編譯後