ArrayList 資料結構分析

浩宇碧海發表於2018-05-18

ArrayList

在Android中的大紅人,經常用到,但是我卻沒有仔細考慮過,為啥直接就new 一個ArrayList,為啥不用LinkedList,什麼情況下用?所以對ArrayList分析一下,學習一下。

程式碼分析:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
複製程式碼
  • 繼承AbstractList<E>類

AbstractList 繼承自 AbstractCollection 抽象類,實現了 List 介面,實現了一些介面如 get,set,remove,且實現了迭代器Iterator,簡化我們要實現一個資料結構的操作,比如實現一個順序表Array,或實現一個連結串列LinkedList等。

  • 實現List<E>介面

List 一個元素有序,可重複,可為null的集合,實現List介面的類,元素通過索引進行排序

  • 實現RandomAccess介面

一個標記介面,支援快速隨機訪問

  • 實現Cloneable介面

支援clone

  • 實現Serizllizable介面

序列化,物件的資訊可以通過網路傳輸,可以持久化寫入儲存區

屬性


    //預設大小
    private static final int DEFAULT_CAPACITY = 10;

    //陣列的實現方式 順序表實現
    transient Object[] elementData;
    
    private int size;
    
複製程式碼

構造方法

//可以傳入容量大小
 public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
    //傳入已有的資料集合
     public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }
複製程式碼

add(E e)

新增一個元素
   public boolean add(E e) {
   //檢查容量
        ensureCapacityInternal(size + 1); 
        // Increments modCount!!
    //末尾新增
        elementData[size++] = e;
        return true;
    }
複製程式碼
  1. 首先檢查陣列大小容量
 private void ensureCapacityInternal(int minCapacity) {
        if (elementData == 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/2
        if (newCapacity - minCapacity < 0)
        //如果擴容大小還不足以容納,採用傳進的大小進行擴容
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        //判斷是否超出了最大了容量限制 
       // private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //將原來的資料copy到新的陣列,並完成擴容操作
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
複製程式碼
  1. 新增元素預設是在末尾新增

add(int index, E element)

指定位置插入指定的元素

 public void add(int index, E element) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        /**
        *引數1:資料來源的陣列
        *引數2:copy的起始位置
        *引數3:新的接收陣列
        *引數4:新的將要賦值的起始位置
        *引數5:copy的長度
        /
        //將index後面的資料後移一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
                         
        //將空出的index位置賦值element
        elementData[index] = element;
        //容量加一
        size++;
    }
複製程式碼

從上面可以看出,在中間位置插入一個元素,時間複雜度為O(n)

get(int index)

  public E get(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
//直接取出陣列index位置的元素
        return (E) elementData[index];
    }
    
複製程式碼

可以看出,Arraylist讀取元素是比較快的O(1)

remove(int index)

  public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        //記錄資料結構改變的次數
        modCount++;
        //獲取index位置的元素
        E oldValue = (E) elementData[index];
        //刪除index的元素,其它元素移動的位數
        int numMoved = size - index - 1;
        //將index後面的元素前移numMoved位
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
        return oldValue;
    }
複製程式碼

##其它常見方法

//獲取元素位置的索引
    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;
    }
    
    //獲取最後一個o的索引
     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;
    }

  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) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(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;
    }

      public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
複製程式碼

從上面的簡單原始碼分析可以看到,ArrayList底層是以個陣列實現的順序表,在插入,修改,刪除的過程中效率不高,O(n) ;在查詢的時候效率較高,讀取速度快O(1),在Android 手機端,我們常見的listview中對讀取的速度要求較高,所以常採用Arraylist.

Other

順序表

將表中元素一個接一個的存入一組連續的儲存單元中,這種儲存結構是順序結構。

transient

java語言的關鍵字,變數修飾符,如果用transient宣告一個例項變數,當物件儲存時,它的值不需要維持。換句話來說就是,用transient關鍵字標記的成員變數不參與序列化過程。

相關文章