本身動手系列——實現一個簡單的ArrayList

ArrayList是Java集合框架中一個經典的實現類。他比起經常使用的數組而言,明顯的優勢在於,能夠隨意的添加和刪除元素而不需考慮數組的大小。處於練手的目的,實現一個簡單的ArrayList,而且把實現的過程在此記錄。
實現的ArrayList主要的功能以下:java

  • 默認構造器和一個參數的有參構造器
  • add方法
  • get方法
  • indexOf方法
  • contains方法
  • size方法
  • isEmpty方法
  • remove方法

這個簡單的ArrayList類 取名爲SimpleArrayList,所有的代碼查看SimpleArrayList代碼git

構造器

源碼ArrayList一共有三個構造器,一個無參構造器,一個參數爲int型有參構造器,一個參數爲Collection型的有參構造器。參數爲Collection型的構造器用來實現將其餘繼承Collection類的容器類轉換成ArrayList。SimpleArrayList類由於尚未手動實現其餘的容器類,因此實現的構造方法只有2個。代碼以下:github

public SimpleArrayList(){
        this(DEFAULT_CAPACITY);
    }


    public SimpleArrayList(int size){
        if (size < 0){
            throw new IllegalArgumentException("默認的大小" + size);
        }else{
            elementData = new Object[size];
        }
    }

無參構造器中的 DEFAULT_CAPACITY是定義的私有變量,默認值是10,用來建立一個大小爲10的數組。有參構造器中,int參數是用來生成一個指定大小的Object數組。將建立好的數組傳給elementDataelementData是真正的用來存儲元素的數組。數組

add方法

add 方法用來往容器中添加元素,add方法有兩個重載方法,一個是add(E e),另外一個是add(int index, E e)。add自己很簡單,可是要處理動態數組,即數組大小不知足的時候,擴大數組的內存。具體的代碼以下:框架

public void add(E e){
        isCapacityEnough(size + 1);
        elementData[size++] = e;
    }

方法isCapacityEnough就是來判斷是否須要擴容,傳入的參數就是最小的擴容空間。由於add一個元素,因此最小的擴容空間,即新的長度是全部元素+ 1。這裏的size就是真正的元素個數。this

private void isCapacityEnough(int size){
        if (size > DEFAULT_CAPACITY){
            explicitCapacity(size);
        }
       if (size < 0){
            throw new OutOfMemoryError();
        }
    }

判斷擴容的方法也很簡單,判斷須要擴容的空間是否是比默認的空間大。若是須要的空間比默認的空間大,就調用explicitCapacity進行擴容。這裏有個size小於0的判斷,出現size小於0主要是由於當size超過Integer.MAX_VALUE就會變成負數。code

private final static int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;

    private void explicitCapacity(int capacity){
        int newLength = elementData.length * 2;
        if (newLength - capacity < 0){
            newLength = capacity;
        }
        if (newLength > (MAX_ARRAY_LENGTH)){
            newLength = (capacity > MAX_ARRAY_LENGTH ? Integer.MAX_VALUE : MAX_ARRAY_LENGTH);
        }
        elementData = Arrays.copyOf(elementData, newLength);
    }

上面的代碼是擴容的代碼,首先,定義一個數組最大的容量的常量爲最大值,這個值按照官方的源碼中的解釋是要有些VM保留了數組的頭部信息在數組中,所以實際存放數據的大小就是整數的最大值 - 8
而後設定一個要擴容的數組的大小,雖然上面說了有一個擴容空間的值 size + 1 ,這個是實際咱們最小須要擴容的大小。但爲了繼續增長元素,而不頻繁的擴容,所以一次性的申請多一些的擴容空間。這裏newLength 打算申請爲 數組長度的2倍,而後去判斷這個長度是否知足須要的擴容空間的值。 即有了後續的兩段代碼繼承

if (newLength - capacity < 0){
            newLength = capacity;
      }
      if (newLength > (MAX_ARRAY_LENGTH)){
            newLength = (capacity > MAX_ARRAY_LENGTH ? Integer.MAX_VALUE : MAX_ARRAY_LENGTH);
      }

若是2倍的長度仍然不知足,則申請到須要的擴容長度。在咱們只增長一個元素的狀況下,這個判斷是永遠不會生效的,可是若是有addAll方法,則增長的元素不少,就要致使一次申請2倍的長度是不夠的。第二個判斷是判斷newLength的長度若是超過上面定義的數組最大長度則判斷要須要的擴容空間是否大於數組最大長度,若是大於則newLength爲 MAX_VALUE ,不然爲 MAX_ARRAY_LENGTH。
最後,真正實現數組擴容到設定長度的方法就沒意思了,調用Arrays.copyOf(elementData, newLength)獲得一個擴容後的數組。
add的另外一個重載方法也很簡單。內存

public void add(int index, E e) {
       //判斷是否是越界
        checkRangeForAdd(index);
        //判斷需不須要擴容
        isCapacityEnough(size + 1);
        //將index的元素及之後的元素向後移一位
        System.arraycopy(elementData,index,elementData,index + 1,size - index);
        //將index下標的值設爲e
        elementData[index] = e;
        size++;
    }
private void checkRangeForAdd(int index){
        //這裏index = size是被容許的,即支持頭,中間,尾部插入
        if (index < 0 || index > size){
            throw new IndexOutOfBoundsException("指定的index超過界限");
        }
    }

至此,一個簡單的add方法就實現完了。ci

get方法

get方法用來獲得容器中指定下標的元素。方法實現比較簡單,直接返回數組中指定下標的元素便可。

private void checkRange(int index) {
        if (index >= size || index < 0){
            throw new IndexOutOfBoundsException("指定的index超過界限");
        }
    }
    public E get(int index){
        checkRange(index);
        return (E)elementData[index];
    }

indexOf方法

indexOf方法用來獲得指定元素的下標。實現起來比較簡單,須要判斷傳入的元素,代碼以下:

public int indexOf(Object o){
        if (o != null) {
            for (int i = 0 ; i < size ; i++){
                if (elementData[i].equals(o)){
                    return i;
                }
            }
        }else {
            for (int i = 0 ; i < size ; i++){
                if (elementData[i] == null) {
                    return i;
                }
            }
        }

        return -1;
    }

判斷傳入的元素是否爲null,若是爲null,則依次與null。若是不爲空,則用equals依次比較。匹配成功就返回下標,匹配失敗就返回-1。

contains方法

contains用來判斷該容器中是否包含指定的元素。在有了indexOf方法的基礎上,contains的實現就很簡單了。

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

size方法

size方法用來獲得容器類的元素個數,實現很簡單,直接返回size的大小便可。

public int size(){
        return size;
    }

isEmpty方法

isEmpty方法用來判斷容器是否爲空,判斷size方法的返回值是否爲0便可。

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

remove方法

remove方法是用來對容器類的元素進行刪除,與add同樣,remove方法也有兩個重載方法,分別是
remove(Object o)和remove(int index)

public E remove(int index) {
        E value = get(index);
        int moveSize = size - index - 1;
        if (moveSize > 0){
            System.arraycopy(elementData,index + 1, elementData,index,size - index - 1);
        }
        elementData[--size] = null;
        return value;
    }
    
    public boolean remove(Object o){
        if (contains(o)){
            remove(indexOf(o));
            return true;
        }else {
            return false;
        }
    }

第一個remove方法是核心方法,首先獲得要刪除的下標元素的值,而後判斷index後面的要前移的元素的個數,若是個數大於零,則調用庫方法,將index後面的元素向前移一位。最後elementData[--size] = null;縮減size大小,並將原最後一位置空。
第二個remove方法不須要向第一個方法同樣,須要告訴使用者要刪除的下標對應的元素,只須要判斷是否刪除成功便可。若是要刪除的元素在列表中,則刪除成功,若是不在則失敗。所以調用contains方法就能夠判斷是否要刪除的元素在列表中。在則調用remove(int index),不在則返回失敗。

總結

自此,一個簡單的ArrayList就實現完了,實現的目的是爲了弄清ArrayList動態數組的原理以及add與remove方法的內容實現。同時,也清楚了ArrayList最大的擴容空間就是Integer的最大值。該類的全部代碼在SimpleArrayList代碼

相關文章
相關標籤/搜索