线性表之顺序表的实现

模拟ArrayList

Posted by HuangCanCan on August 17, 2019

前言

今天主要是来模拟Java中ArrayList的基本功能。

List接口

首先定义List接口

/**
* 线性表接口
*
* @ClassName List
* @Description 线性表接口
* @Author HuangCanCan
* @Date 2019/8/11 15:30
* @Version 1.0
**/
public interface List {
    /**
    * 返回线性表的大小,即数据元素的个数。
    */
    public int size();

    /**
    * 返回线性表中索引为 index的数据元素
    */
    public Object get(int index);

    /**
    * 如果线性表为空返回 true,否则返回 false。
    */
    public boolean isEmpty();

    /**
    * 判断线性表是否包含数据元素 e
    */
    public boolean contains(Object e);

    /**
    * 返回数据元素 e 在线性表中的序号
    */
    public int indexOf(Object e);

    /**
    * 将数据元素 e 插入到线性表中 index 索引的位置
    */
    public void add(int index, Object e);

    /**
    * 将数据元素 e 插入到线性表末尾
    */
    public void add(Object e);

    /**
    * 将数据元素 e 插入到元素 obj 之前
    */
    public boolean addBefore(Object obj, Object e);

    /**
    * 将数据元素 e 插入到元素 obj 之后
    */
    public boolean addAfter(Object obj, Object e);

    /**
    * 删除线性表中索引为 index 的元素,并返回原数据元素
    */
    public Object remove(int index);

    /**
    * 删除线性表中第一个与 e 相同的元素
    */
    public boolean remove(Object e);

    /**
    * 替换线性表中索引为 index的数据元素为 e,返回原数据元素
    */
    public Object replace(int index, Object e);
}

ArrayList功能

其次继承List接口,实现功能

import java.util.Arrays;

/**
* 顺序表,模拟ArrayList
* <p>
* 底层采用的数组,但是长度可以动态变化
* <p>
* java.util.ArrayList底层的扩容机制是: 每次增长50%(原来的数组长度右移一位)
*
* @ClassName ArrayList
* @Description 顺序表,模拟ArrayList
* @Author HuangCanCan
* @Date 2019/8/11 15:37
* @Version 1.0
**/
public class ArrayList implements List {
    /**
    * 底层是一个数组,目前还没有确定长度
    */
    private Object[] elementData;

    /**
    * 元素的个数
    * <p>
    * 不是数组分配了几个空间,而是元素的个数
    * <p>
    * size的大小,小于或者等于elementData数组的大小
    */
    private int size;

    public ArrayList() {
        this(8);//初始化大小为8

    }

    /**
    * @param initialCapacity 指定数组的初始长度
    */
    public ArrayList(int initialCapacity) {
        //给数组分配指定数量的空间
        this.elementData = new Object[initialCapacity];
        //指定顺序表的元素个数,默认是0
        //size=0;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public Object get(int index) {
        if (index < 0 || index >= size) {
            throw new RuntimeException("数组索引越界异常:" + index);
        }
        return this.elementData[index];
    }

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

    @Override
    public boolean contains(Object e) {
        return indexOf(e) >= 0;
    }

    @Override
    public int indexOf(Object e) {//返回第一次出现的指定元素的索引
        if (e == null) {
            for (int i = 0; i < size; i++) {
                if (elementData[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (e.equals(elementData[i])) {
                    return i;
                }
            }
        }
        return -1; //找不到元素就返回负数
    }

    @Override
    public void add(int index, Object e) {
        if (index < 0 || index >= size) {
            throw new RuntimeException("数组索引越界异常:" + index);
        }
        /*
        * 第一步:数组elementData的动态扩容
        * 数组扩容条件:数组满的时候 size == elementData.length
        * 扩容策略:增长一倍 或 增长一半
        * java.util.ArrayList底层的扩容机制是: 每次增长50%(原来的数组长度右移一位)
        */
        if (size == elementData.length) {
            grow();
        }
        /*
        * 第二部:  后移i及其后面元素,从最后一个元素开e始
        */
        for (int j = size; j > index; index--) {
            elementData[j] = elementData[j - 1];
        }

        /*
        * 第三步:给数组elementData第i个位置赋值赋值,元素个数size + 1
        */
        this.elementData[index] = e; //给数组赋值
        size++;//元素个数 + 1

    }

    @Override
    public void add(Object e) {
        /*
        * 第二步:数组elementData的动态扩容
        * 数组扩容条件:数组满的时候 size == elementData.length
        * 扩容策略:增长一倍 或 增长一半
        * java.util.ArrayList底层的扩容机制是: 每次增长50%(原来的数组长度右移一位)
        */
        if (size == elementData.length) {
            grow();
        }

        /*
        * 第一步:给数组elementData赋值,元素个数size + 1
        */
//        this.elementData[size] = e; //给数组赋值
//        size++;//元素个数 + 1
        this.elementData[size++] = e; //上面两句等同于这句

        System.out.println("length=" + elementData.length);

        //add(i,e) 是添加元素到指定位置,这个add(e)是添加元素到数组末尾,是add(i,e)方法特例,可以如下:
//        this.add(size,e);
    }

    private void grow() {
//        //创建一个新的数组,长度是就数组的两倍
//        Object[] newArr = new Object[elementData.length * 2];
//        //将旧数组的数据拷贝到新数组
//        for (int i = 0; i < size; i++) {
//            newArr[i] = elementData[i];
//        }
//        //让elementData指向新数组
//        elementData = newArr;

        //上面的步骤等同于下面这句,利用jdk提供的Arrays.copyOf方法
        elementData = Arrays.copyOf(elementData, elementData.length * 2);
    }

    @Override
    public boolean addBefore(Object obj, Object e) {//将数据元素 e 插入到元素 obj 之前,元素e是第一次出现的位置
        /**
        * 第一:找到元素obj的索引
        * 第二:把元素obj后面的元素向后移动一个位置(包含obj元素),
        * 第三步:把元素e放到obj元素的位置
        * 其实就是创建一个新数组,把元素obj前面的元素依次放到新数组中相应的位置;把元素e放到obj元素原来的位置;
        * 把包含元素obj以及obj后面的元素放到这个新数组中e元素的后面。
        * 新数组的长度为数组元素的个数+1
        */

        int index = indexOf(obj);
        if (index < 0) {
//            throw new RuntimeException("找不到该元素:" + obj);
            return false;
        }
        Object[] newArr = new Object[size + 1];
        for (int i = 0; i < size; i++) {
            if (i < index) {
                newArr[i] = elementData[i];
            }
            if (i == index) {
                newArr[i] = e;
                newArr[i + 1] = elementData[i];
            }
            if (i > index) {
                newArr[i + 1] = elementData[i];
            }
        }
        elementData = newArr;
        size++;
        return true;
    }

    @Override
    public boolean addAfter(Object obj, Object e) {//将数据元素 e 插入到元素 obj 之后,元素e是第一次出现的位置
        /**
        * 第一:找到元素obj的索引
        * 第二:把元素obj后面的元素往后移(不包含元素obj)
        * 第三:把元素e放到obj元素后一个位置
        */
        int index = indexOf(obj);
        if (index < 0) {
            return false;
        }
        Object[] newArr = new Object[size + 1];
        for (int i = 0; i < size; i++) {
            if (i <= index) {
                newArr[i] = elementData[i];
            }
            newArr[i + 1] = elementData[i];

            if (i == size - 1) {
                newArr[index + 1] = e;
            }
        }
        elementData = newArr;
        size++;

        return true;
    }

    @Override
    public Object remove(int index) {
        if (index >= size) {
            throw new RuntimeException("索引越界异常:" + index);
        }
        Object oldValue = elementData[index];

        /**
        * 将该索引i后面的元素向前移动一位
        */
//        Object[] newArr = new Object[size + 1];
//
//        for (int x = 0; x < size; x++) {
//            newArr[x] = elementData[x];
//            if (x >= index) {
//                newArr[x] = elementData[x + 1];
//            }
//        }
//        elementData = newArr;

//            上面注释的部分就等同于以下部分:
        int numMoved = size - index - 1;//需要移动删除的元素个数
        if (numMoved > 0)
            System.arraycopy(elementData, index + 1, elementData, index,
                    numMoved);

        size--;
        return oldValue;
    }

    @Override
    public boolean remove(Object e) {//删除第一个与 e 相同的元素
        int index = indexOf(e);
        if (index < 0) {
            return false;
        }
        remove(index);
        return true;
    }

    @Override
    public Object replace(int index, Object e) {//替换索引为 index 的数据元素为 e,返回原数据元素
        if (index >= size) {
            throw new RuntimeException("索引越界异常:" + index);
        }
        Object oldValue = elementData[index];
        elementData[index] = e;
        return oldValue;
    }

    @Override
    public String toString() {
        if (size == 0) {
            return "[]";
        }
        StringBuilder builder = new StringBuilder("[");
        for (int i = 0; i < size; i++) {
            if (i != size - 1) {
                builder.append(elementData[i] + ",");
            } else {
                builder.append(elementData[i]);
            }
        }
        builder.append("]");
        return builder.toString();
    }
}

总结:
ArrayList的底层是数组实现的,所以插入元素,删除元素需要移动元素,效率低。 删除、修改、查询元素的时间复杂度是 T(n) = O(n)

这里中重要的是add()方法的扩容机制,可以去浏览一下jdk的ArrayList的源码,它的底层扩容机制是:每次增长50%(原来的数组长度右移一位),也就是增长一半。 这里我的扩容机制是:增长一倍。 扩容的条件是:当数组里的元素装满时(size == elementData.length)