數(shù)據(jù)結(jié)構(gòu)(順序表)的應(yīng)用——ArrayList、Vector分析

上篇我們提到了順序表在平常開發(fā)中的應(yīng)用,現(xiàn)在我們只重點(diǎn)分析ArrayList及Vector的實(shí)現(xiàn)。

ArrayList想必大家都已經(jīng)很熟悉了,但是我們只是在外面調(diào)用接口,有的人并不知道其內(nèi)部的實(shí)現(xiàn)原理。我以前也是遇到集合都用ArrayList,但是用了那么多次也沒有對(duì)其有深入的了解,現(xiàn)在我們就一起來分析。

ArrayList是基于數(shù)組實(shí)現(xiàn)的,所以里面有很多操作都是通過數(shù)組來操作的,它是一個(gè)動(dòng)態(tài)數(shù)組,其容量能自動(dòng)增長(zhǎng),類似于C語言中的動(dòng)態(tài)申請(qǐng)內(nèi)存,動(dòng)態(tài)增長(zhǎng)內(nèi)存。ArrayList不是線程安全的,只能在單線程環(huán)境下,多線程環(huán)境下可以考慮用Collections.synchronizedList(List l)函數(shù)返回一個(gè)線程安全的ArrayList類,也可以使用concurrent并發(fā)包下的CopyOnWriteArrayList類。

(一)ArrayList的繼承關(guān)系與實(shí)現(xiàn)的接口

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

  --------------------------------------------------------------------------------------------------

  public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>

   --------------------------------------------------------------------------------------------------

  public abstract class AbstractCollection<E> implements Collection<E> 

從ArrayList<E>可以看出它是支持泛型的,ArrayList繼承的是AbstractList,而AbstractList又繼承了AbstractCollection,AbstractCollection是Collection接口的實(shí)現(xiàn)類,里面封裝了一些集合的通用接口;而AbstractList可以說是實(shí)現(xiàn)List接口的一個(gè)頂層實(shí)現(xiàn)類,里面也定義了一些有關(guān)List接口在對(duì)Collection的接口基礎(chǔ)上的拓展接口。我們通常使用的ArrayList就是AbstractList其中的一個(gè)子類。

ArrayList實(shí)現(xiàn)了List接口,所以ArrayList必須實(shí)現(xiàn)List接口里面封裝的方法。

ArrayList實(shí)現(xiàn)了RandomAccess接口,支持快速隨機(jī)訪問。

ArrayList實(shí)現(xiàn)了Cloneable接口,使得ArrayList能被克隆,

通過實(shí)現(xiàn) java.io.Serializable 接口以啟用其序列化功能。未實(shí)現(xiàn)此接口的類將無法使其任何狀態(tài)序列化或反序列化。序列化接口沒有方法或字段,僅用于標(biāo)識(shí)可序列化的語義。

(二)ArrayList的屬性字段

  private static final int DEFAULT_CAPACITY = 10;     //ArrayList默認(rèn)的初始容量

  private static final Object[] EMPTY_ELEMENTDATA = {};  //用于空實(shí)例的共享空數(shù)組實(shí)例

ArrayList定義了兩個(gè)屬性,咋一看這不是我們的順序表的結(jié)構(gòu)嗎?
其中transient關(guān)鍵字表示的字段的生命周期僅存于調(diào)用者的內(nèi)存中而不會(huì)寫到磁盤持久化。

  transient Object[] elementData;    //存放ArrayList的元素?cái)?shù)組

  private int size; 

(三)ArrayList的構(gòu)造函數(shù)

(1)帶有初始容量的構(gòu)造函數(shù)

      public ArrayList(int initialCapacity) {
            super();          //調(diào)用分類AbstractList的空構(gòu)造
            if (initialCapacity < 0)
                  throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
            this.elementData = new Object[initialCapacity];      //根據(jù)傳進(jìn)來的初始容量建表
       }

(2)無參構(gòu)造函數(shù)

     public ArrayList(){    //可能有些Api的寫法不一樣,但思想都是構(gòu)造了一個(gè)初始容量為10的數(shù)組
        super();
        this.elementData = EMPTY_ELEMENTDATA;   
     }

(3)無參構(gòu)造函數(shù)

    //將集合c中的元素全部復(fù)制到表中
    public ArrayList(Collection<? extends E> c){
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        //這個(gè)是谷歌工程師寫的,他說c的toArray方法有可能不返回一個(gè)數(shù)組,這里是一個(gè)bug
        if(elementData.getClass() != Object[].class){
            //數(shù)組的copy操作在這里就不分析了,可以查看Arrays的源碼
            elementData = Arrays.copyOf(elementData,size,Object[].class);
        }
    }

(四)ArrayList的增刪改查

(1)添加操作

    //在數(shù)組尾部添加元素e
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //先確保數(shù)組容量是否夠用
        elementData[size++] = e;
        return true;
    }

    //在指定的位置index添加元素e
    public void add(int index,E e){
        rangeCheckForAdd(index);   //查看要添加的index是否越界
        ensureCapacity(size+1);
        //將index位置(包含)之后的數(shù)組全部往右移動(dòng)一位
        System.arraycopy(elementData,index,elementData,index+1,size-index);   //性能慢就是體現(xiàn)在這里
        elementData[index] = e;
        size++;
    }

    //將集合c添加到ArrayList中的指定位置
    public boolean addAll(int index,Collection<? extends E> c){
        rangeCheckForAdd(index);
        Object[] o = c.toArray();
        int numNew = o.length;  
        int numMoved = size - index; //數(shù)組要移動(dòng)的位數(shù)
        if(numMoved>0){
            //將index位置(包含)之后的數(shù)組全部往右移動(dòng)numNew位
            System.arraycopy(elementData,index,elementData,index+numNew,numMoved);
        }
        System.arraycopy(o,0,elementData,index,numNew);
        size += numNew;
        return numMoved != 0;
    }

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {  //如果空表
             //取默認(rèn)容量和數(shù)組要達(dá)到的最小容量的最大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 
        }

        ensureExplicitCapacity(minCapacity);    //確保容量
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;      //這個(gè)是List的改變次數(shù),在ArrayList的父類上定義的,可以不必太過理會(huì)

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)  //如果傳進(jìn)來的容量大于數(shù)組長(zhǎng)度,就說明數(shù)組的容量不夠用了
            grow(minCapacity);    //增長(zhǎng)容量
        }

      //ArrayList的擴(kuò)容機(jī)制
      private void grow(int minCapacity) {
          // overflow-conscious code
          int oldCapacity = elementData.length;    //原來的容量
          int newCapacity = oldCapacity + (oldCapacity >> 1);    //定義新的容量為原來的3/2倍
          if (newCapacity - minCapacity < 0)    //如果新的容量都不夠用,直接將要增長(zhǎng)的容量作為數(shù)組的新的容量
              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);  //把原來的數(shù)組復(fù)制到新的數(shù)組中,還是用elementData 表示,并指定新的容量
      }
      
      //MAX_ARRAY_SIZE=Integer.MAX_VALUE - 8;
      private static int hugeCapacity(int minCapacity) {
          if (minCapacity < 0) // overflow
              throw new OutOfMemoryError();
          return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
      }
    
      private void rangeCheckForAdd(int index){
          if(index<0 || index>size){
              throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size);
          }
      }

(2)刪除操作

    //刪除數(shù)組中指定位置的元素
    public E remove(int index){
        rangeCheck(index);    //為要?jiǎng)h除和查找的Index查看是否越界
        E oldValue = (E)elementData[index];
          //需要復(fù)制元素的個(gè)數(shù),也就是index后面的元素個(gè)數(shù)
        int numMoved = size - index -1;
        if(numMoved>0){
            //將index后面的元素全部往前移動(dòng)1個(gè)位置
            System.arraycopy(elementData,index+1,elementData,index,numMoved);
        }
        //經(jīng)過arraycopy的移位,數(shù)組容器的最個(gè)位置被騰空,但是仍然持有某個(gè)對(duì)象的引用,需要把這個(gè)多余的引用置為null.
        elementData[--size] = null;
        return oldValue;
    }

    //刪除數(shù)組中的對(duì)象
    public boolean remove(Object o){
        if(o == null){    //對(duì)象為Null的刪除方法
            for(int index=0;index<size;index++){
                fastRemove(index);
                return true;
            }
        }else{
            for(int index=0;index<size;index++){
                if(elementData[index].equals(o)){
                    fastRemove(index);
                    return true;
                }
             }
        }
        return false;
    }

    //快速刪除 這是個(gè)內(nèi)部方法,數(shù)組越界已經(jīng)在上個(gè)方法中判斷,這里就不再進(jìn)行判斷了
    private void fastRemove(int index){
        int numMoved = size - index -1;
        if(numMoved > 0){
            System.arraycopy(elementData,index+1,elementData,index,numMoved);
        }
        elementData[--size] = null;
    }

    private void rangeCheck(int index){
        if(index<0 || index>=size){
            throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size);
        }
    }

(3)修改操作

    public E set(int index,E e){
        rangeCheck(index);    //檢查下標(biāo)越界問題
        E oldValue = (E)elementData[index];
        elementData[index] = e;    //直接將當(dāng)前位置的元素進(jìn)行替換
        return oldValue;
    }

(4)查詢操作

    public E get(int index){
        rangeCheck(index);
        E oldValue = (E)elementData[index];
        return oldValue;
    }

(五)ArrayList定義的一些集合方法和數(shù)組的下表查詢方法

    public int size() {
        return size;
    }

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

    //清空數(shù)組
    public void clear() {
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }

    //將當(dāng)前容量值設(shè)置為實(shí)際元素個(gè)數(shù)
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
        }
    }
    
    //判斷數(shù)組中是否包含這個(gè)對(duì)象
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    //返回對(duì)象在數(shù)組中的下標(biāo)(第一次出現(xiàn)的)
    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;
    }

    //返回對(duì)象在數(shù)組中最后一次出現(xiàn)的下標(biā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;
    }

這些就是ArrayList的大概了,還有遍歷和迭代器沒分析,但是我覺得那不是最重要的,重要的是了解ArrayList的數(shù)據(jù)結(jié)構(gòu)和增刪改查操作還有擴(kuò)容機(jī)制。這些也是面試中可能遇到的問題,只有了解了其中的實(shí)現(xiàn)原理,我們才能在面試官面前對(duì)答如流,還有提升自己。有些方法可能在個(gè)版本的不一樣,我這里是在用Android SDK里面的源碼進(jìn)行分析的,除非有版本變了數(shù)據(jù)結(jié)構(gòu),否則實(shí)現(xiàn)原理都是一樣的。對(duì)于Vector我們不必太過深究,因?yàn)楝F(xiàn)在基本看不到使用了。

(六)ArrayList和Vector
還有Vector沒分析,但其實(shí)Vector的實(shí)現(xiàn)原理和ArrayList基本上是一模一樣的,只是Vector在實(shí)現(xiàn)增刪改查等等操作時(shí)添加了synchronized關(guān)鍵字,在多線程情況下是安全的。

List接口下一共實(shí)現(xiàn)了三個(gè)類:ArrayList,Vector,LinkedList。LinkedList就不多說了,它一般主要用在保持?jǐn)?shù)據(jù)的插入順序的時(shí)候。ArrayList和Vector都是用數(shù)組實(shí)現(xiàn)的,主要有這么三個(gè)區(qū)別:

1、Vector是多線程安全的,而ArrayList不是,這個(gè)可以從源碼中看出,Vector類中的方法很多有synchronized進(jìn)行修飾,這樣就導(dǎo)致了Vector在效率上無法與ArrayList相比;

2、兩個(gè)都是采用的線性連續(xù)空間存儲(chǔ)元素,但是當(dāng)空間不足的時(shí)候,兩個(gè)類的增加方式是不同的,很多網(wǎng)友說Vector增加原來空間的一倍,ArrayList增加原來的1.5倍。

    //ArrayList的擴(kuò)容
       private void grow(int minCapacity) {
           // overflow-conscious code
          int oldCapacity = elementData.length;
          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);
      }

     // Vector擴(kuò)容,這個(gè)擴(kuò)容需要做個(gè)判斷:如果容量增量初始化的不是0,即使用的public Vector(int initialCapacity,int capacityIncrement)構(gòu)造方法進(jìn)行的初始化,那么擴(kuò)容的容量是(oldCapacity+capacityIncrement),就是原來的容量加上容量增量的值; 
      //如果沒有設(shè)置容量增量,那么擴(kuò)容后的容量就是(oldCapacity+oldCapacity),就是原來容量的二倍。
      private void grow(int minCapacity) {
         // overflow-conscious code
         int oldCapacity = elementData.length;
         int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

3、Vector可以設(shè)置增長(zhǎng)因子,而ArrayList不可以,最開始看這個(gè)的時(shí)候,我沒理解什么是增量因子,不過通過對(duì)比一下兩個(gè)源碼理解了這個(gè),先看看兩個(gè)類的構(gòu)造方法:
ArrayList有三個(gè)構(gòu)造方法:

    public ArrayList(int initialCapacity)//構(gòu)造一個(gè)具有指定初始容量的空列表。  
    public ArrayList()//構(gòu)造一個(gè)初始容量為10的空列表。  
    public ArrayList(Collection<? extends E> c)//構(gòu)造一個(gè)包含指定 collection 的元素的列表  

Vector有四個(gè)構(gòu)造方法:

    public Vector()//使用指定的初始容量和等于零的容量增量構(gòu)造一個(gè)空向量。  
    public Vector(int initialCapacity)//構(gòu)造一個(gè)空向量,使其內(nèi)部數(shù)據(jù)數(shù)組的大小,其標(biāo)準(zhǔn)容量增量為零。  
    public Vector(Collection<? extends E> c)//構(gòu)造一個(gè)包含指定 collection 中的元素的向量  
    public Vector(int initialCapacity,int capacityIncrement)//使用指定的初始容量和容量增量構(gòu)造一個(gè)空的向量  

(七)ArrayList總結(jié)

(1)ArrayList是線性表中的順序存儲(chǔ)結(jié)構(gòu)的順序表,因?yàn)閮?nèi)部維護(hù)的是一個(gè)數(shù)組,數(shù)組是一個(gè)擁有連續(xù)存儲(chǔ)地址的存儲(chǔ)塊。

(2)ArrayList因?yàn)閮?nèi)部維護(hù)的是一個(gè)數(shù)組,查詢和修改的效率很高,尾部插入效率也高,但是插入添加和刪除的效率比較低,性能變慢具體體現(xiàn)在數(shù)組的copy上,特別是數(shù)據(jù)量大的情況下必較明顯。

(3)ArrayList在添加元素的時(shí)候是允許加入null元素的。但我們?cè)谔砑訑?shù)據(jù)的時(shí)候最好對(duì)數(shù)據(jù)進(jìn)行非空判斷,否則取出來的數(shù)據(jù)在使用的時(shí)候空指針分分鐘教你做人。

(4)ArrayList的是線程不安全的,盡量不要在多線程環(huán)境下使用ArrayList。

請(qǐng)老司機(jī)指出不足之處,謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,623評(píng)論 18 399
  • 面向?qū)ο笾饕槍?duì)面向過程。 面向過程的基本單元是函數(shù)。 什么是對(duì)象:EVERYTHING IS OBJECT(萬物...
    sinpi閱讀 1,217評(píng)論 0 4
  • Java源碼研究之容器(1) 如何看源碼 很多時(shí)候我們看源碼, 看完了以后經(jīng)常也沒啥收獲, 有些地方看得懂, 有些...
    駱駝騎士閱讀 1,056評(píng)論 0 22
  • 昨天晚上,做完飯,小妞邀請(qǐng)我一起玩兒,我看到她自己做的泡泡水,居然吹出了超級(jí)無敵大的泡泡,欣喜。還教我一些新花樣,...
    如水心情_3473閱讀 201評(píng)論 0 0
  • 其實(shí)大概一個(gè)月前我才在喜馬拉雅上把《盜墓筆記》聽完。聽了差不多一個(gè)月,每晚睡覺前把音頻下載好,第二天起來去上班的路...
    賤賤小姐閱讀 431評(píng)論 0 0

友情鏈接更多精彩內(nèi)容