Java究極打基礎之ArrayList篇

本篇主要介紹ArrayList的用法和源碼分析,基於jdk1.8,先從List接口開始。java


List

List接口定義了以下方法:數組

int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
void add(int index, E element);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
boolean remove(Object o);
E remove(int index);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
E get(int index);
E set(int index, E element);
int indexOf(Object o);
int lastIndexOf(Object o);
List<E> subList(int fromIndex, int toIndex);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);

乍一看,這麼多方法。其實不少方法是一樣的功能,方法重載而已。
接下來逐個介紹下List定義的方法。dom

  • size 得到List元素的個數
  • isEmpty 判斷List是否爲空
  • contains/containsAll 判斷List中是否有該元素,或者有該集合中的全部元素
  • iterator 得到迭代器對象用於迭代
  • toArray 將List轉換成數組
  • add/addAll 添加元素至List中,默認直接添加到最後,也能夠選擇指定的位置,還能夠添加整個集合
  • remove/removeAll 刪除元素,能夠根據元素刪除,也能夠根據索引刪除,還能夠根據集合刪除
  • retainAll 取與目標集合的交集
  • clear 清空List的全部元素
  • get 根據索引得到元素
  • set 覆蓋索引處的元素
  • indexOf 得到該元素的索引
  • lastIndexOf 得到該元素的索引(從後往前)
  • subList 得到子List根據start 和 end
  • listIterator 得到ListIterator對象

方法名言簡意賅,基本上均可以從方法名知道方法的目的。
接下來分析List的經常使用實現類:
ArrayList
LinkedList
Vectoride

本篇介紹ArrayList源碼分析

ArrayList類圖

圖片描述

在個人理解中,ArrayList是一個封裝的數組,提供了一些便利的方法供使用者使用,規避了使用原生數組的風險。性能

ArrayList的定義

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

繼承了AbstractList即成爲了List,就要實現定義的全部方法。
實現了RandomAccess接口 就是提供了隨機訪問能力,能夠經過下標得到指定元素
實現了Cloneable接口 表明是可克隆的,須要實現clone方法
實現了Serializable接口 表明ArrayList是可序列化的學習

下面介紹下ArrayList的主要方法ui

添加元素

boolean add(E e)
先附上源碼this

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

此方法的執行邏輯:spa

  1. 判斷數組長度是否夠,不夠則擴容。默認擴容1.5倍
  2. elementData[size++] = e;將新元素添加進去。
private void ensureCapacityInternal(int minCapacity) {
            //是不是空List,是則使用初始容量擴容
            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;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//擴容1.5倍,採用位運算
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);//最大容量就是Integer的最大值
        // minCapacity is usually close to size, so this is a win:
        //採用Arrays的copyOf進行深拷貝,其中調用的本地方法System.arraycopy,此方法是在內存中操做所以速度會很快。
        elementData = Arrays.copyOf(elementData, newCapacity);
        //至此擴容結束
    }

void add(int index, E element)方法稍有不一樣

  1. 首先檢查index是否合法
  2. 擴容
  3. 將新元素插入指定位置
public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //將index -> (size -1)的元素都日後移動一位 
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

boolean addAll(Collection<? extends E> c)
boolean addAll(int index, Collection<? extends E> c)
這兩個方法和上面的add大同小異,第一步都是判斷容量,並擴容。
容量大小從1變爲c的length,elementData[index] = element;賦值也變爲數組拷貝,
直接上代碼,秒懂。

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

add總結

  • 不管是哪一種add,都須要先執行方法 ensureCapacityInternal(size + 1) 進行容量判斷及擴容,確保以後的操做不會產生數組越界。
  • 建議在咱們平常工做時,若是大概知道元素的數量,能夠在初始化的時候指定大小,這樣能夠減小擴容的次數,提高性能。

刪除元素

E remove(int index)

  1. 檢查是否索引不正確。
  2. 計算移動的元素的個數,數組往前移動。
  3. 將以前第size位置的元素置空,返回被刪除的元素。
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;
    }

boolean remove(Object o)

  1. 區分要刪除的元素是null仍是非null。
  2. 找到該元素的索引,執行上面方法邏輯的二、3步。
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
    }

這種方法依賴元素的equals方法,循環遍歷數組,由於ArrayList是容許null元素的,因此不管是使用if (o.equals(elementData[index])) 仍是 if (elementData[index].equals(o)) 都可能產生空指針,因此單獨對null進行處理,邏輯都是同樣的。
奇怪的是這個fastRemove方法,本來覺得會有些特殊處理,結果發現代碼和上面remove(int index)中的如出一轍,爲何上面的remove中不調用這個fastRemove呢?難道寫兩個remove方法的不是同一人?邏輯不影響,只是代碼冗餘了一點。

boolean removeAll(Collection<?> c)

public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }

刪除存在於目標集合的方法,使用了batchRemove(Collection<?> c, boolean complement)這個方法,這個方法做了封裝,在retainAll(Collection<?> c)取並集這個方法也有使用。
retainAll中是這樣使用的:

return batchRemove(c, true);

和咱們的removeAll只差第二個參數boolean complement,remove是false,retail是true,那究竟是什麼意思呢?complement的原意的補充,在這裏我理解爲保留,remove就不保留,retail就保留,接着咱們分析batchRemove這個方法。

private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

定義了 r 和 w兩個int,r用於遍歷原來的數組,w的意思是新的數組的size。
假如定義2個數組用於舉例,
數組1,五個元素 1,2,3,4,5
數組2,五個元素 3,4,6,7,8

數組1調用removeAll(數組2)
此時進入batchRemove,咱們來一步一步走看r,w如何變化
此時complement是false,即數組2中沒有數組1中第r個元素才知足if條件

  1. r = 0, w = 0, elementData[r] = 1, c.contains(elementData[r]) = false.
    執行elementData[w++] = elementData[r],
    數組1:1,2,3,4,5
  2. r = 1, w = 1, elementData[r] = 2, c.contains(elementData[r]) = false.
    執行elementData[w++] = elementData[r],
    數組1:1,2,3,4,5
  3. r = 2, w = 2, elementData[r] = 3, c.contains(elementData[r]) = true.
    此時if條件不知足了,w不自增了,表明elementData[r]要不存在與新數組中,要被刪除,因此跳過了。
  4. r = 3, w = 2, elementData[r] = 4, c.contains(elementData[r]) = true.
    if條件依舊不知足,w不自增,此元素也是要刪除。
  5. r = 4, w = 2, elementData[r] = 5, c.contains(elementData[r]) = false.
    執行elementData[w++] = elementData[r],
    數組1:1,2,5,4,5
    此時for循環結束,elementData數組目前是這樣的,接着執行finally中的代碼,

首先執行

// Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }

咱們的狀況是 r=size,那何時r會不等於size呢,jdk中寫了註釋,就是在if判斷時,調用數組2的contains方法,可能會拋空指針等異常。這時數組尚未遍歷完,那r確定是小於size的。
那沒判斷的那些數據還要不要處理?保守起見jdk仍是會將他保存在數組中,由於最終w是做爲新的size,因此w加上了沒處理過的個數size - r。

接着執行下面一段

if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }

這段代碼意圖很清晰,w由於是新的size值,因此將w及其以後的都置位null,增長修改次數,
給size賦予新值以後就結束了。

remove總結

  • 刪除元素儘可能使用指定下標的方法,性能好。
  • 每次刪除都會進行數組移動(除非刪除最後一位),若是頻繁刪除元素,請使用LinkedList

boolean contains(Object o)
contains 底層調用的是indexOf方法

public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

int indexOf(Object o)

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

就是循環遍歷,一個個比對,有則返回對應下標,無則返回-1
對應的lastIndexOf是從最後往前遍歷。

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

E get(int index)

rangeCheck(index);

        return elementData(index);

先檢查index,再返回數組對應元素。

E set(int index, E element)

public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

先檢查index,在覆蓋index的元素,返回舊元素。

void clear()

modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;

增長修改次數,將全部元素置空,size設置爲0,須要注意的是,數組的大小是沒有變化的。

int size()
size方法就是直接將size變量直接返回

public int size() {
        return size;
    }

boolean isEmpty()
判斷size是否等於0

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

迭代器

Iterator<E> iterator()

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

如代碼所示,建立了一個Itr對象並返回,接下來看Itr類的定義

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

首先介紹下三個成員變量

  1. cursor,表明下一個訪問元素的在elementData數組中的索引,初始值是0。
  2. lastRet,表明上一個訪問元素在elementData數組中的索引,初始值是-1。
  3. expectedModCount,預期的modcount的值,在Itr對象建立的時候從父類ArrayList的modcount得到,用於判斷在使用該對象迭代時,有麼有對數組進行修改,有則會拋出ConcurrentModificationException。

日常經常使用的迭代器方法
hasNext()
就是判斷當前索引是否等於size。

E next()
首先檢查list是否被修改過,expectedModCount是在建立對象時就得到了,若是在以後對list進行了其餘修改操做的話,modCount就會增長,就會拋出ConcurrentModificationException。
沒有異常就按下表返回座標,cursor自增,lastRet也自增。

void remove()

首先判斷是否能刪除,若能則調用父類的remove方法,刪除元素,接着會更新cursor和lastRet。
最重要的是會更新expectedModCount,此時調用了父類的remove方法,會使modCount+1,因此更新了 expectedModCount,讓後續的檢查不會拋異常。

ListIterator<E> listIterator

listIterator和iterator同樣,只不過listIterator有更多的方法,至關於iterator的增強版。
定義以下

private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            cursor = index;
        }

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

        public E previous() {
            checkForComodification();
            try {
                int i = cursor - 1;
                E previous = get(i);
                lastRet = cursor = i;
                return previous;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public int nextIndex() {
            return cursor;
        }

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

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.set(lastRet, e);
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                AbstractList.this.add(i, e);
                lastRet = -1;
                cursor = i + 1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

沒有什麼須要注意的地方,基本的方法都從Iterator中繼承過來並添加一些方法。

List<E> subList(int fromIndex, int toIndex)

這又是一個內部類

class SubList<E> extends AbstractList<E> {
    private final AbstractList<E> l;
    private final int offset;
    private int size;

    SubList(AbstractList<E> list, int fromIndex, int toIndex) {
        if (fromIndex < 0)
            throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
        if (toIndex > list.size())
            throw new IndexOutOfBoundsException("toIndex = " + toIndex);
        if (fromIndex > toIndex)
            throw new IllegalArgumentException("fromIndex(" + fromIndex +
                                               ") > toIndex(" + toIndex + ")");
        l = list;
        offset = fromIndex;
        size = toIndex - fromIndex;
        this.modCount = l.modCount;
    }
    。
    。
    。
}

持有了父類的引用,內部的全部方法都是經過父類的這個引用去完成的,
因此須要注意的是,subList不是返回一個新的List,仍是原來的引用,因此改變subList的數據,原有的數據也會更改。

終於把基本的方法都介紹完了,從源碼的角度分析了全部的方法,感受對ArrayList知根知底了,用它時確定會更加駕輕就熟了,看了源碼纔有原來實現都這麼簡單啊這樣的感受,不過從中也學習到了大牛規範的代碼風格,良好的結構,可讀性很高。下篇分析List的另外一個實現類,LinkedList。

相關文章
相關標籤/搜索