四、Java數據結構之ArrayList

要點速記: 擴容=1.5倍+1,數組和鏈表的區別

1. 類結構

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

1.1、 實現接口

  • RandmoAccess接口:

是java中用來被List實現,爲List提供快速訪問功能的。在ArrayList中,我們即可以通過元素的序號快速獲取元素對象;這就是快速隨機訪問
想詳細瞭解的呢可以看RandomAccess接口的使用

  • Cloneable接口:
    即覆蓋了函數clone(),能被克隆
  • Serializable接口
    意味着ArrayList支持序列化,能通過序列化去傳輸
  • List接口:
    在Java集合分類中,List代表有序,可重複
    具體和下面父類一起說
  • 父類AbstractList
    ArrayList 繼承了AbstractList,父類也實現了List。它是一個數組隊列,提供了相關的添加、刪除、修改、遍歷等功能

1.2、 它跟數組的關係?

1.2.1、數組VS鏈表

Array(數組),數據結構適合內存結構也是連續的(適合遍歷),隨機訪問(適合查找),不適合增刪(鏈表則相反),同時在創建數組的同時你必須指定大小
想知道詳細的可以看看數組與鏈表的優缺點

1.3.2、 數組 VS ArrayList

  1. Array類型的變量在聲明的同時必須進行實例化(至少得初始化數組的大小),而ArrayList可以只是先聲明。
  2. Array只能存儲同構的對象,而ArrayList可以存儲異構的對象。
    List內部就是使用"object[] _items;"這樣一個私有字段來封裝對象的)
  3. 在CLR託管對中的存放方式:
    Array是始終是連續存放的,而ArrayList的存放不一定連續。
  4. 初始化大小:
    Array對象的初始化必須只定指定大小,且創建後的數組大小是固定的,
    而ArrayList的大小可以動態指定,其大小可以在初始化時指定,也可以不指定,也就是說該對象的空間可以任意增加。
  5. Array不能夠隨意添加和刪除其中的項,而ArrayList可以在任意位置插入和刪除項。
    同構和異構:
    同構的對象是指類型相同的對象,若聲明爲int[]的數組就只能存放整形數據,string[]只能存放字符型數據,但聲明爲object[]的數組除外。
    而ArrayList可以存放任何不同類型的數據(因爲它裏面存放的都是被裝箱了的Object型對象,實際上Array
    回到正文了,去看看ArrayList源碼羅

2、探索源碼

2.1、 構造


// ArrayList帶容量大小的構造函數。
public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
    // 新建一個數組
    this.elementData = new Object[initialCapacity];
}

// ArrayList構造函數。默認容量是10。
public ArrayList() {
    this(10);
}

// 構造一個包含指定元素的list,這些元素的是按照Collection的迭代器返回的順序排列的
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    size = elementData.length;
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}

其中構造方法牽扯到了兩個屬性

// 保存ArrayList中數據的數組
private transient Object[] elementData;
// ArrayList中實際數據的數量
private int size;

(1) elementData :Object[]類型的數組,它保存了添加到ArrayList中的元素。
實際上,elementData是個動態數組,我們能通過構造函數 ArrayList(int initialCapacity)來執行它的初始容量爲initialCapacity;
如果通過不含參數的構造函數ArrayList()來創建ArrayList,則elementData的容量默認是10。elementData數組的大小會根據ArrayList容量的增長而動態的增長
具體的增長方式,請參考源碼分析中的ensureCapacity()函數。
(2) size 則是動態數組的實際大小。

2.2、添加

    /**
     * 添加一個元素
     */
    public boolean add(E e) {
       // 進行擴容檢查
       ensureCapacity( size + 1);  // Increments modCount
       // 將e增加至list的數據尾部,容量+1
        elementData[size ++] = e;
        return true;
    }

    /**
     * 在指定位置添加一個元素
     */
    public void add(int index, E element) {
        // 判斷索引是否越界,這裏會拋出多麼熟悉的異常。。。
        if (index > size || index < 0)
           throw new IndexOutOfBoundsException(
               "Index: "+index+", Size: " +size);

       // 進行擴容檢查
       ensureCapacity( size+1);  // Increments modCount  
       // 對數組進行復制處理,目的就是空出index的位置插入element,並將index後的元素位移一個位置
       System. arraycopy(elementData, index, elementData, index + 1,
                      size - index);
       // 將指定的index位置賦值爲element
        elementData[index] = element;
       // list容量+1
        size++;
    }
    /**
     * 增加一個集合元素
     */
    public boolean addAll(Collection<? extends E> c) {
       //將c轉換爲數組
       Object[] a = c.toArray();
        int numNew = a.length ;
       //擴容檢查
       ensureCapacity( size + numNew);  // Increments modCount
       //將c添加至list的數據尾部
        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(
               "Index: " + index + ", Size: " + size);

       Object[] a = c.toArray();
        int numNew = a.length ;
       ensureCapacity( size + numNew);  // Increments modCount

       // 計算需要移動的長度(index之後的元素個數)
        int numMoved = size - index;
       // 數組複製,空出第index到index+numNum的位置,即將數組index後的元素向右移動numNum個位置
        if (numMoved > 0)
           System. arraycopy(elementData, index, elementData, index + numNew,
                          numMoved);

       // 將要插入的集合元素複製到數組空出的位置中
        System. arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

    /**
     * 數組容量檢查,不夠時則進行擴容
     */
   public void ensureCapacity( int minCapacity) {
        modCount++;
       // 當前數組的長度
        int oldCapacity = elementData .length;
       // 最小需要的容量大於當前數組的長度則進行擴容
        if (minCapacity > oldCapacity) {
           Object oldData[] = elementData;
          // 新擴容的數組長度爲舊容量的1.5倍+1
           int newCapacity = (oldCapacity * 3)/2 + 1;
          // 如果新擴容的數組長度還是比最小需要的容量小,則以最小需要的容量爲長度進行擴容
           if (newCapacity < minCapacity)
              newCapacity = minCapacity;
            // minCapacity is usually close to size, so this is a win:
            // 進行數據拷貝,Arrays.copyOf底層實現是System.arrayCopy()
            elementData = Arrays.copyOf( elementData, newCapacity);
       }
    }

2.3、 刪除

    /**
     * 根據索引位置刪除元素
     */
    public E remove( int index) {
      // 數組越界檢查
       RangeCheck(index);

        modCount++;
      // 取出要刪除位置的元素,供返回使用
       E oldValue = (E) elementData[index];
       // 計算數組要複製的數量
        int numMoved = size - index - 1;
       // 數組複製,就是將index之後的元素往前移動一個位置
        if (numMoved > 0)
           System. arraycopy(elementData, index+1, elementData, index,
                          numMoved);
       // 將數組最後一個元素置空(因爲刪除了一個元素,然後index後面的元素都向前移動了,所以最後一個就沒用了),好讓gc儘快回收
       // 不要忘了size減一
        elementData[--size ] = null; // Let gc do its work

        return oldValue;
    }

    /**
     * 根據元素內容刪除,只刪除匹配的第一個
     */
    public boolean remove(Object o) {
       // 對要刪除的元素進行null判斷
       // 對數據元素進行遍歷查找,知道找到第一個要刪除的元素,刪除後進行返回,如果要刪除的元素正好是最後一個那就慘了,時間複雜度可達O(n) 。。。
        if (o == null) {
            for (int index = 0; index < size; index++)
              // null值要用==比較
               if (elementData [index] == null) {
                  fastRemove(index);
                  return true;
              }
       } else {
           for (int index = 0; index < size; index++)
              // 非null當然是用equals比較了
               if (o.equals(elementData [index])) {
                  fastRemove(index);
                  return true;
              }
        }
        return false;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;
       // 原理和之前的add一樣,還是進行數組複製,將index後的元素向前移動一個位置,不細解釋了,
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System. arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size ] = null; // Let gc do its work
    }

    /**
     * 數組越界檢查
     */
    private void RangeCheck(int index) {
        if (index >= size )
           throw new IndexOutOfBoundsException(
               "Index: "+index+", Size: " +size);
    }

2.4 更新

    /**
     * 將指定位置的元素更新爲新元素
     */
    public E set( int index, E element) {
       // 數組越界檢查
       RangeCheck(index);

       // 取出要更新位置的元素,供返回使用
       E oldValue = (E) elementData[index];
       // 將該位置賦值爲行的元素
        elementData[index] = element;
       // 返回舊元素
        return oldValue;
    }

2.5查找

    /**
     * 查找指定位置上的元素
     */
    public E get( int index) {
       RangeCheck(index);

        return (E) elementData [index];
    }
    

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


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

contains主要是檢查indexOf,也就是元素在list中出現的索引位置也就是數組下標,再看indexOf和lastIndexOf代碼是不是很熟悉

沒錯,和public boolean remove(Object o) 的代碼一樣,都是元素null判斷,都是循環比較,不多說了。。。但是要知道,最差的情況(要找的元素是最後一個)也是很慘的。。。

3. ArrayList3種遍歷方式

(01) 第一種,通過迭代器遍歷。即通過Iterator去遍歷。

Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();
}

(02) 第二種,隨機訪問,通過索引值去遍歷。
由於ArrayList實現了RandomAccess接口,它支持通過索引值去隨機訪問元素。

Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
    value = (Integer)list.get(i);        
}

(03) 第三種,for循環遍歷。如下:

Integer value = null;
for (Integer integ:list) {
    value = integ;
}

總結迭代速度是最慢的,隨機訪問是最快得

4 總結

ArrayList和LinkedList的區別

  • ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。
  • 對於隨機訪問get和set,ArrayList覺得優於LinkedList,因爲LinkedList要移動指針。
  • 對於新增和刪除操作add和remove,LinkedList比較佔優勢,因爲ArrayList要移動數據。

ArrayList和Vector的區別

  • Vector和ArrayList幾乎是完全相同的,唯一的區別在於Vector是同步類(synchronized),屬於強同步類。因此開銷就比ArrayList要大,訪問要慢。正常情況下,大多數的Java程序員使用ArrayList而不是Vector,因爲同步完全可以由程序員自己來控制。
  • Vector每次擴容請求其大小的2倍空間,而ArrayList是1.5倍。
  • Vector還有一個子類Stack.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章