1. 程式人生 > >Java還要再學一遍基礎(六)ArrayList詳解

Java還要再學一遍基礎(六)ArrayList詳解

ArrayList概要。

  • ArrayList是一個容量可變的動態陣列,繼承了AbstractList,並且AbstractList已經實現了一些基本的增刪改查,ListIterator等功能,ArrayList更關注的是內部的的陣列實現。
  • ArrayList是實現RandomAccess介面,RandomAccess介面中並沒有任何的方法,只是表明具有快速隨機訪問的功能,也就是通過Index(索引)訪問。
  • ArrayList中的元素可以重複,可以為空。
  • ArrayList是執行緒不安全的,併發環境可以使用Vector或者CopyOnWriteList。
  • ArrayList的查詢和修改效率很高,但是插入和刪除效率不高。

原始碼解析(基於JDK1.8)

1. 重要屬性

//用於儲存元素的陣列,並且不能序列化
transient Object[] elementData;
//元素的數量
private int size;

可以看到ArrayList內部確實是用陣列實現的。

2. 建構函式

 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_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); } } 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; } }

提供空參的構造方法和指定容量的構造方法,和初始資料的構造方法。
並且空參的構造方法中還沒有指定容量。

3. 關鍵的方法

add方法:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        //如果elementData中沒有資料,則取(10, minCapacity)中大的作為容量
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //新的容量為原來的1.5倍
        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);
    }

在呼叫add方法的時候,首先會呼叫ensureCapacityInternal方法去判斷容量。並且在當前容量小於10的情況下會以10作為容量,基本可以說預設容量為10, 如果超出則擴容到原來的1.5倍(oldCapacity >> 1)右移一位,相當於除以2.

Override了Clone方法:

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

同樣克隆之前必須先呼叫父類的super.clone()申請空間。然後江資料中的資料拷貝到新的ArrayList中去,再將修改次數modCount置為零。

toArray方法
ArrayList提供了兩個toArray方法

 public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

@SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

一個是返回的Object[] 一個是返回的指定的泛型的陣列。

List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 100; i++){
            list.add(i);
        }
        Integer[] a = (Integer[])list.toArray();

上面這段程式碼看似沒問題但是會報 java.lang.ClassCastException異常,原因是java的陣列不能直接強轉。

        List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 100; i++){
            list.add(i);
        }
        //Integer[] a = (Integer[])list.toArray();

        Integer[] a  = new Integer[list.size()];
        list.toArray(a);

上面這段程式碼便沒有問題。

remove方法:

public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        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

        return oldValue;
    }


public boolean remove(Object o) {
        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) {
        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
    }

remove方法基本沒什麼特別的,把對應位置的元素刪除其實也就是把該元素以前和以後的元素重新組成一個新的陣列,這樣也導致刪除的時候效率很低。

4. ArrayList中的ListIterator和Iterator

  • Iterator是Collection大家族中的一個迭代器,Iterator是一個介面,其中定義了hasNext,next,和remove方法等。
  • ListIterator是專門用於List集合的迭代器,也是一個介面,同時提供了hasPrevious,previous,add,set等方法,能夠倒序遍歷而且還可以增加元素等,功能更加齊全。

ArrayList中的ListIterator實現:

  • 首先ArrayList裡面有一個private的class叫做Itr,實現了Iterator介面。這個介面很明顯是為了提供基本的Iterator的迭代器功能。
 private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        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];
        }

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

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } 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();
        }

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

實現方法很簡單,利用一個遊標記錄當前的位置,再配合size做判斷從而進行迭代。

  • ArrayList中還提供了一個private的class叫做ListItr,繼承了Itr,同時實現了ListIterator介面,裡面實現了倒序迭代,增加,修改等功能。
private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            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中提供的public的方法即可獲取相應的迭代器:

public Iterator<E> iterator() {
        return new Itr();
    }

public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

- ArrayList中遍歷的時候操作元素的坑

坑1:
例:

        List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 10; i++){
            list.add(i);
        }

        for(int i = 0; i < list.size(); i++){
            if(list.get(i) % 5 == 0)
                list.remove(i);
        }

        for(int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }

上面的程式碼沒有問題,輸出:

1 2 3 4 6 7 8 9 

看下面一段程式碼:

    List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 10; i++){
            list.add(1);
        }

        for(int i = 0; i < list.size(); i++){
            if(list.get(i) == 1)
                list.remove(i);
        }

        for(int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }

輸出:

1 1 1 1 1 

並不是所有等於1的元素都被remove了。
實際假設index為0的位置的元素的值為1這個時候1被remove,同時從index為1開始的後面的元素都會向前挪一個位置,這個時候原來在index為1的位置的元素跑到了0的位置,所以沒有遍歷到。

解決辦法,倒序遍歷,因為無論後面的怎麼變,我們判斷的都是前面不變的元素:

    List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 10; i++){
            list.add(1);
        }

        for(int i = list.size() - 1; i >= 0 ; i--){
            if(list.get(i) == 1)
                list.remove(i);
        }

        for(int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }

坑2:
例:

        List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 10; i++){
            list.add(i);
        }

        int len = list.size();
        for(int i = 0; i < len ; i++){
            if(list.get(i) % 5 == 0)
                list.remove(i);
        }

        for(int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }

執行丟擲異常:java.lang.IndexOutOfBoundsException
因為之前的迴圈條件是i < list.size(),而在remove之後size是會變小的,但是通過size方法獲取到的都是正確的所以沒問題,這裡的迴圈條件一直都是len也就是remove之前的,所以丟擲異常。

坑3
例:

        List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 10; i++){
            list.add(i);
        }

        for(Iterator<Integer> it = list.iterator(); it.hasNext();){
            int i = it.next();
            if(i % 5 == 0)
                list.remove(i);
        }

        for(int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }

執行:丟擲java.util.ConcurrentModificationException
這個異常的意思是在迭代的時候list發生了預想之外的改變,
看一下Iterator中的next,和remove方法:

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

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

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

兩個方法中都要先呼叫checkForComodification方法

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

如果modCount不等於expectedModCount就拋異常。
最後再看看ArrayList中的remove方法:

    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        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

        return oldValue;
    }

其中有一個很明顯的modCount++的操作。
不難得出,這個一場就是由於呼叫ArrayList的remove方法的時候modCount發生改變從而與Iterator中的expectedModCount不相等,丟擲異常。
解決辦法?—》 使用Iterator提供的remove方法即可。

        List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 10; i++){
            list.add(i);
        }

        for(Iterator<Integer> it = list.iterator(); it.hasNext();){
            int i = it.next();
            //呼叫Iterator的remove
            if(i % 5 == 0)
                it.remove();
        }

        for(int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }

輸出:

1 2 3 4 6 7 8 9 

* * ArrayList中的subList坑**

  • subList返回的是ArrayList的內部類SubList,而不是ArrayList,所以不能強轉成ArrayList,會丟擲ClassCastException,同時對與返回的subList的操作會反映到元表上。
    例:
List<Integer> list = new ArrayList<>(150);
for(int i = 0; i < 10; i++){
    list.add(i);
}

for(Iterator<Integer> it = list.iterator(); it.hasNext();){
    int i = it.next();
    System.out.print(i + " ");
}

System.out.println();

List<Integer> subList =  list.subList(0, list.size());

for(int i = 0; i < 10; i++){
    subList.set(i, i + 1);
}

for(Iterator<Integer> it = list.iterator(); it.hasNext();){
    int i = it.next();
    System.out.print(i + " ");
}

輸出:

0 1 2 3 4 5 6 7 8 9 
1 2 3 4 5 6 7 8 9 10