一、简介

在项目中,相信大家都已经用过集合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,不能保证没有问题,只是帮助理解实现原理而已。仅供大家学习参考,一起学习一起进步。

10-03 16:21