1. 程式人生 > >1 手寫ArrayList核心原始碼

1 手寫ArrayList核心原始碼

手寫ArrayList核心原始碼

ArrayList是Java中常用的資料結構,不光有ArrayList,還有LinkedList,HashMap,LinkedHashMap,HashSet,Queue,PriorityQueue等等,我們將手寫這些常用的資料結構的核心原始碼,用盡量少的程式碼來揭示核心原理。

下面我們來手寫ArrayList的核心原始碼

首先我們定義一個QArrayList,不要問為什麼叫QArrayList,因為我之前寫過Qt,僅此而已。原始碼 public class<T> QArrayList,Java中的ArrayList的底層就是用一個Object[] 結構來儲存資料的。我們也要定義一個Object[] 屬性。

而且我們還要定義一個預設的資料的大小,以便在呼叫預設建構函式的情況下使用。
private final int DEFAULT_LIST_SIZE = 8;

還要定義一個 int mSize 變數,mSize 預設為0代表下一個可以存放資料的陣列的索引代表下一個可以存放資料的陣列的索引代表下一個可以存放資料的陣列的索引
重要的事情說三遍

到現在為止我們的類如下:

public class QList<T> {
    //預設的陣列的大小
    private final int DEFAULT_LIST_SIZE = 8;

    //存放資料的地方
    private Object[] mData;
    
    //下一個可以存放資料的當前陣列的索引
    private int mSize;
    
    ......
}    

好了,存放資料的陣列也有了,下一個可以存放資料的當前的陣列的索引也有了
ArrayList 底層是用陣列存放資料,那麼會有一個問題,如果此時陣列滿了我們再往裡面存放資料的時候,怎麼辦呢?ArrayList是再新建一個數組,新陣列的大小是原來陣列大小的2倍,那麼我們也這樣做。

此時,我們實現 add,get,remove,resize等這幾個核心方法,QArrayList完整的程式碼如下 :

public class QArrayList<T> {
    //預設的陣列的大小
    private final int DEFAULT_LIST_SIZE = 8;

    //存放資料的地方
    private Object[] mData;

    //下一個可以存放資料的當前陣列的索引
    private int mSize;

    public QArrayList() {
        //new 一個數組,用來存放
        mData = new Object[DEFAULT_LIST_SIZE];

        //下一個可以存放資料的當前陣列的索引為0
        mSize = 0;
    }

    public QArrayList(int capacity){
        if(capacity <= 0 || capacity > Integer.MAX_VALUE){
            throw new RuntimeException("invalid capacity");
        }

        mData = new Object[capacity];
        mSize = 0;
    }

    //返回當時陣列的已經存放了多少個元素
    public int size() {
        return mSize;
    }

    //返回陣列的總大小,其實這個介面沒有必要對外提供,這裡我們只是為了演示用
    public int capacity() {
        return mData.length;
    }

    //新增一個元素
    public void add(T e) {
        //規定不允許新增一個空元素
        if(e == null){
            return;
        }

        //如果當前陣列已經滿了,擴容為原來陣列的2倍
        if (mSize >= mData.length) {

            //擴容
            resize();
        }

        //將新增的元素新增到陣列中
        mData[mSize] = e;

        //同時 mSize++ 指向下一個可以存放資料的位置
        mSize++;
    }

    //獲取指定位置的元素,如果position不合法,直接丟擲異常
    //這樣做是有必要的,我們提供的是一個庫
    // 直接丟擲異常讓使用知道用錯了,沒有必要 return null
    // 因為這是個庫,不是業務,就算return null,也是業務層的事
    public T get(int position) {
        if (position < 0 || position >= mData.length) {
            throw new RuntimeException("position is invalid");
        }

        // position 大於 mSize 也沒有關係,因為也是返回null,證明沒有獲取到
        return (T) mData[position];
    }

    //刪除指定位置的元素
    public T remove(int position) {
        //和上面一樣,位置不合法直接丟擲異常
        if (position < 0 || position >= mData.length) {
            throw new RuntimeException("position is invalid");
        }

        //把當前要刪除的元素儲存下來,最後返回要刪除的元素
        T e = (T) mData[position];

        //刪除後,把後面的所有元素都往前移位
        for (int i = position + 1; i < mData.length; i++) {
            mData[i - 1] = mData[i];
        }

        //別忘了 mSize 要 --
        mSize--;

        //返回刪除的元素
        return e;
    }

    //刪除指定的元素
    public boolean remove(T e) {
        //因為陣列可能沒有滿,如果刪除的是null,沒有必要,我們不允許
        if (e == null) {
            return false;
        }

        //找到刪除元素的位置
        int position = -1;
        for (int i = 0; i < mData.length; i++) {
            if (e == mData[i] || e.equals(mData[i])) {
                position = i;
                break;
            }
        }

        //沒有找到就返回
        if (position == -1) {
            return false;
        }

        //刪除
        return remove(position) != null;
    }

    //擴容,我們都以2倍的容量擴容
    private void resize() {
        Object[] old = mData;
        mData = new Object[mData.length * 2];
        for (int i = 0; i < old.length; i++) {
            mData[i] = old[i];
        }

        old = null;
    }
}

註釋都有相關的解釋
我們來測試,測試程式碼如下:

    public static void main(String[] args) {
        QArrayList<String> list = new QArrayList<>();
        list.add("tom");
        list.add("jim");
        list.add("lilei");
        list.add("hanmeimei");

        System.out.println("list.get(2)=" + list.get(2));
        System.out.println("list.size()=" + list.size());
        for (int i = 0; i < list.size(); i++) {
            System.out.println("list.get(" + i + ") = " + list.get(i));
        }

        System.out.println("=======================");
        System.out.println("演示刪除操作");
        list.remove("jim");

        for (int i = 0; i < list.size(); i++) {
            System.out.println("list.get(" + i + ") = " + list.get(i));
        }
    }
    

輸出如下:
list.get(2)=lilei
list.size()=4
list.get(0) = tom
list.get(1) = jim
list.get(2) = lilei
list.get(3) = hanmeimei
============
演示刪除操作
list.get(0) = tom
list.get(1) = lilei
list.get(2) = hanmeimei

但是最重要的擴容功能還沒有演示,下面是擴容演示的測試程式碼:

擴容前 : list.capacity()=2
擴容後 : list.capacity()=4
list.get(0) = tom
list.get(1) = jim
list.get(2) = lilei
list.get(3) = hanmeimei

可以看到,我們新建了一個底層只有2個元素的陣列,但是我們添加了4個元素,我們打印出擴容後的陣列的容量是 4 ,可見我們的擴容機制是沒有問題的。

以上就是QArrayList的核心原理,我們下節手寫LinkedList的核心原理