1. 程式人生 > >Java集合Collection之實現原理解讀(ArrayList)

Java集合Collection之實現原理解讀(ArrayList)

一、簡介

在專案中,相信大家都已經用過集合List,它提供了一系列的API,方便我們使用。今天有空去看了下ArrayList的原始碼,本章將會模仿原始碼實現一個簡單的ArrayList,只是幫助理解ArrayList底層是怎麼實現的,並沒有必要去自定義ArrayList。

二、實現原理

ArrayList底層是通過陣列實現的,所有對資料的操作其實底層都是對Object[]陣列的操作, 具體的增刪查改等功能都是依據這個陣列進行陣列相關的操作,如複製陣列、陣列擴容、刪除陣列中某個元素等。

特點:

* 1. ArrayList查詢快(索引),新增、修改、刪除慢(需要修改下標)
* 2. ArrayList預設建立大小為10的物件陣列,如果超過十個元素會進行擴容。

三、自定義ArrayList

package com.wsh.arraylist;

/**
 * 自定義一個簡單的ArrayList,幫助理解ArrayList底層原理
 *
 * @author weishihuai
 * @date 2018/9/25
 * <p>
 * 說明:
 * 1. ArrayList底層實現是陣列,所有對資料的操作其實底層都是對Object[]陣列的操作。
 * 2. ArrayList查詢快(索引),新增、修改、刪除慢(需要修改下標)
 * 3. ArrayList預設建立大小為10的物件陣列,如果超過十個元素會進行擴容。
 * 4. 本類只實現了部分常見的方法,幫助自己理解而已。
 */
public class CustomArrayList {

    /**
     * 陣列初始化大小(預設為10)
     */
    private static final int DEFAULT_INIT_NUM = 10;

    /**
     * 陣列的大小
     */
    private int size;

    /**
     * 存放資料的物件陣列
     */
    private Object[] data;

    /**
     * 無參構造方法
     */
    public CustomArrayList() {
        this(DEFAULT_INIT_NUM);
    }

    /**
     * 有參構造方法
     *
     * @param initNum 指定大小
     */
    public CustomArrayList(int initNum) {
        //如果初始化大小小於0
        if (initNum < 0) {
            throw new IllegalArgumentException("陣列初始化大小不能小於0");
        }
        //建立指定大小的物件陣列
        data = new Object[initNum];
    }

    /**
     * 新增元素
     *
     * @param object 物件
     */
    public void add(Object object) {
        //陣列擴容處理
        //建立新陣列,複製原陣列到新陣列,返回新的陣列
        if (size == data.length) {
            Object[] newData = new Object[size * 2 + 1];
//            for(int i = 0 ; i < data.length ; i++) {
//                newData[i] = data[i];
//            }
            //拷貝陣列
            System.arraycopy(data, 0, newData, 0, data.length);
            //替換原陣列
            data = newData;
        }


        data[size] = object;
        //每次新增完元素大小需要加1
        size++;
    }

    /**
     * 獲取List的大小
     *
     * @return List的大小
     */
    public int size() {
        return size;
    }

    /**
     * List是否為空
     *
     * @return
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 根據下標獲取對應的值
     *
     * @param index 下標索引
     * @return
     */
    public Object get(int index) {
        //判斷索引是否合法
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("陣列下標索引非法,下標越界");
        }
        return data[index];
    }

    /**
     * 根據索引刪除指定位置的物件
     * 原理: 被刪除下標以後的元素依次往前移
     *
     * @param index 下標索引
     * @return 被刪除的舊資料
     */
    public Object remove(int index) {
        Object old = data[index];
        //判斷索引是否合法
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("陣列下標索引非法,下標越界");
        }
        //原理:陣列copy
        int num = size - index - 1;
        if (num > 0) {
            System.arraycopy(data, index + 1, data, index, num);
        }

        data[--size] = null;
//        data[size - 1] = null;
//        size--;

        //返回被刪除的舊資料
        return old;
    }

    /**
     * 刪除指定值的元素
     * 原理: 迴圈遍歷陣列,使用equals比較值是否相等,找到第一個匹配的陣列元素的下標,根據該下標進行刪除
     *
     * @param object 指定值
     * @return
     */
    public boolean remove(Object object) {
        for (int i = 0; i < size; i++) {
            if (get(i).equals(object)) {
                remove(i);
                return true;
            }
        }
        return false;
    }

    /**
     * 給指定索引下標賦值
     * 原理:替換原陣列下標的值
     * 下標索引
     *
     * @param index
     * @param object 物件
     */
    public Object set(int index, Object object) {
        //判斷索引是否合法
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("陣列下標索引非法,下標越界");
        }

        Object oldValue = data[index];
        data[index] = object;
        return oldValue;
    }

    /**
     * 指定位置新增值
     * 原理: 陣列copy,依次往後移,空出當前index位置存放object物件
     *
     * @param index  下標索引
     * @param object 物件
     */
    public void add(int index, Object object) {
        //判斷索引是否合法
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("陣列下標索引非法,下標越界");
        }

        //陣列的擴容
        if (size == data.length) {
            Object[] newData = new Object[size * 2 + 1];
            //拷貝陣列
            System.arraycopy(data, 0, newData, 0, data.length);
            //替換原陣列
            data = newData;
        }

        System.arraycopy(data, index, data, index + 1, size - index);
        data[index] = object;
        size++;
    }

    /**
     * 根據指定值查詢下標,未找到返回-1
     * 原理: 迴圈遍歷陣列,使用equals方法判斷值是否相等,返回第一個匹配的下標索引,未找到返回-1
     *
     * @param object 物件
     * @return 下標索引
     */
    public int indexOf(Object object) {
        if (null == object) {
            for (int i = 0; i < size; i++) {
                if (null == data[i]) {
                    return i;
                }
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (object.equals(data[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * 從後往前找指定值對應的下標索引
     * 原理:  倒序迴圈遍歷陣列,使用equals方法判斷是否相等,找到返回對應的下標索引,未找到返回-1
     *
     * @param object 指定值
     * @return 下標索引
     */
    public int lastIndexOf(Object object) {
        if (null == object) {
            for (int i = size - 1; i >= 0; i--) {
                if (data[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = size - 1; i >= 0; i--) {
                if (object.equals(data[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

}

以上就是一個簡單的ArrayList,提供了簡單的增刪查改功能,可能會有一些問題,不過幫助理解底層實現已經夠了。

四、方法詳解

【a】構造方法:

1. 無參構造方法: 預設建立長度為10的Object[]陣列。

2. 有參構造方法:建立指定大小的Object[]陣列。  

【b】public void add(Object object) {}   新增元素

1. 新增元素之前需要判斷當前Object[]陣列的長度是否等於size,如果相等了說明陣列需要擴容了,簡單理解就是數組裡面不能存放這麼多元素了,需要擴大陣列的長度,一般是2n+1。

2. 新增實現原理: 實際上是建立一個新的陣列,然後將原來Object[]陣列中的元素拷貝到新陣列中,然後把新陣列賦值給存放資料的陣列。

3. 每新增一個元素,size需要加1

【c】size(){}  返回list的大小

1. size

【d】isEmpty() {}   判斷list是否為空,

1. 即size == 0

【e】get(int index){}        根據索引取出對應list中的元素

1. 需要判斷索引是否越界以及是否合法

2. 實現原理: 根據索引,這個索引對應陣列的下標,即直接返回data[index]陣列下標對應的值

【f】remove(int index) {}    移出list中對應索引的元素

1. 判斷索引index引數是否合法

2.實現原理: 陣列拷貝,即將對應index索引的元素後面的元素都前移一位,然後把index索引的元素置空,利於垃圾回收器進行無用物件回收。

【g】remove(Object object){}  移出list中指定物件的元素

1. 實現原理:  迴圈遍歷陣列,使用equals比較值是否相等,找到第一個匹配的陣列元素的下標,根據該下標進行刪除。

【h】set(int index, Object object) {}  給list中對應索引的元素賦新值

1. 實現原理: 通過data[index]找到對應索引的元素,直接將原物件的值賦新值

2. 判斷索引是否合法

【i】add(int index, Object object) {}  list中指定位置新增元素

1. 實現原理:  陣列copy,依次往後移,空出當前index位置存放object物件

2. 陣列擴容問題

3. 判斷索引是否合法

【j】indexOf(Object object){}  判斷list中是否含有某個元素

1. 實現元素: 迴圈遍歷陣列,使用equals方法判斷值是否相等,返回第一個匹配的下標索引,未找到返回-1

2. 注意object為空的情況

【k】lastIndexOf(Object object) {}  從list後面開始查詢是否包含某個元素。

1. 實現原理:  倒序迴圈遍歷陣列,使用equals方法判斷是否相等,找到返回對應的下標索引,未找到返回-1

2. 注意object為空的情況

五、測試

package com.wsh.arraylist;

/**
 * 測試自定義ArrayList
 *
 * @author weishihuai
 * @date 2018/9/25
 */
public class TestCustomArrayList {

    public static void main(String[] args) {
        CustomArrayList customArrayList = new CustomArrayList();

        /***********************************add(object)***************************************/
        customArrayList.add("aaa");
        customArrayList.add("bbb");
        customArrayList.add("ccc");
        customArrayList.add("ddd");

        /***********************************size()********************************************/
        //4
        System.out.println(customArrayList.size());

        /***********************************isEmpty()*****************************************/
        //false
        System.out.println(customArrayList.isEmpty());

        /***********************************get(index)****************************************/
        //Exception in thread "main" java.lang.IllegalArgumentException: 陣列下標索引非法,下標越界
        //System.out.println(customArrayList.get(10));
        //ccc
        System.out.println(customArrayList.get(2));

        /***********************************remove(index)*************************************/
        //ccc
        System.out.println(customArrayList.remove(2));
        //3
        System.out.println(customArrayList.size());
        //true
        System.out.println(customArrayList.remove("bbb"));
        //2
        System.out.println(customArrayList.size());

        /***********************************set(index,object)*********************************/
        //ddd
        System.out.println(customArrayList.get(1));
        //ddd
        System.out.println(customArrayList.set(1, "1111"));
        //1111
        System.out.println(customArrayList.get(1));

        /***********************************add(index,object)*********************************/
        customArrayList.add(1, "2222");
        //2222
        System.out.println(customArrayList.get(1));
        for (int i = 0; i < customArrayList.size(); i++) {
            //aaa=====2222=====1111=====
            System.out.print(customArrayList.get(i) + "=====");
            System.out.println();
        }

        /***********************************indexOf(object)************************************/
        //1
        System.out.println(customArrayList.indexOf("2222"));
        //-1
        System.out.println(customArrayList.indexOf("abcdef"));

        /***********************************lastIndexOf(object)********************************/
        //0
        System.out.println(customArrayList.lastIndexOf("aaa"));
    }

}

至此,我們已經通過模擬ArrayList原始碼自定義了一個簡單的List,雖然只實現了一部分方法,但對於我們理解ArrayList底層是怎麼實現的應該會有一定的幫助。

六、總結

ArrayList集合類底層是通過陣列實現的,具體的操作都是圍繞陣列展開,涉及陣列拷貝、陣列新增、陣列刪除元素等。本文是作者在看原始碼之後仿照原始碼實現的一個ArrayList,不能保證沒有問題,只是幫助理解實現原理而已。僅供大家學習參考,一起學習一起進步。