java基础-手动实现ArrayList

        对于java开发者来说,大概用得最多的容器就是ArrayList了。所以掌握ArrayList的底层实现,还是比较重要的。一般面试的时候也是常见的问题。

        废话不多说,直接上代码。注释也很详细。首先是定义一个List接口。这样我们后面可以实现ArrayList,也可以实现LinkedList。

一、定义List接口

package com.zwh.linertable;

/**
 * @Auther: zwh
 * @Date: 2018/12/26 09:39
 * @Description:定义操作列表的接口方法
 * @Version:1.0
 */
@SuppressWarnings("all")
public interface List {
    /**
     * @description : 获取线性表的长度
     * @param:
     * @return: 长度
     * @auther: zwh
     * @date: 2018/12/26 9:39
     */
    public int size();

    /**
     * @description : 获取指定索引的内容
     * @param: 索引位置
     * @return: 指定位置的内容
     * @auther: zwh
     * @date: 2018/12/26 9:40
     */
    public Object get(int index);

    /**
     * @description : 判断当前容器是否为空
     * @param:
     * @return:
     * @auther: zwh
     * @date: 2018/12/26 9:41
     */
    public boolean isEmpty();
    
    /**
     * @description :判断当前容器是否包含当前对象 
     * @param: 
     * @return: 
     * @auther: zwh
     * @date: 2018/12/26 9:41
     */
    public boolean contains(Object element);
    
    /**
     * @description :往容器中添加一个对象 
     * @param: 
     * @return: 
     * @auther: zwh
     * @date: 2018/12/26 9:41
     */
    public void add(Object element);
    
    /**
     * @description : 
     * @param: 
     * @return: 
     * @auther: zwh
     * @date: 2018/12/26 9:42
     */
    public void remove(Object element);
    
    /**
     * @description : 从容器中删除一个对象
     * @param: 要删除对象的索引
     * @return: 
     * @auther: zwh
     * @date: 2018/12/26 9:44
     */
    public void remove(int index);
    
    /**
     * @description : 添加一个对象到指定位置
     * @param: i:要添加对象的索引位置,e:要添加的对象
     * @return: 
     * @auther: zwh
     * @date: 2018/12/26 9:45
     */
    public void add(int index,Object element);
    
    /**
     * @description : 获取某个对象在容器中的索引
     * @param: 对象
     * @return: 索引值
     * @auther: zwh
     * @date: 2018/12/26 9:45
     */
    public int indexOf(Object elemente);

    /**
     * @description : 在指定对象前添加一个对象
     * @param: obj:添加对象的位置,e:要添加的对象
     * @return:
     * @auther: zwh
     * @date: 2018/12/26 9:46
     */
    public void addBefore(Object obj,Object element);
   
    /**
     * @description : 在指定对象后添加一个对象
     * @param:obj:添加对象的位置,e:要添加的对象
     * @return: 
     * @auther: zwh
     * @date: 2018/12/26 10:01
     */
    public void addAfter(Object obj,Object element);
    
   /**
    * @description : 替换制定位置的对象
    * @param: i:要替换对象的位置,e:要替换的新的对象
    * @return:
    * @auther: zwh
    * @date: 2018/12/26 9:58
    */
    public Object replace(int index,Object element);
}

这个接口中,基本包含了大部分的java.util中List的功能。然后一个一个去实现它。

二、使用ArrayList实现List

1、新建一个ArrayList类,实现List接口。

/**
 * @Auther: zwh
 * @Date: 2018/12/26 10:05
 * @Description: 线性表,底层采用数组,长度可以动态变化
 * @Version:1.0
 */
@SuppressWarnings("all")
public class ArrayList implements List {
}

      2、在这个实现类中,一个一个的去实现接口中定义的方法。ArrayList底层采用数组来实现。所以我们提前定义好成员变量:

    /**
     * 存储当前线性表内容
     */
    private Object[] elementData;
    /**
     * 存储当前线性表长度
     */
    private int size;
    /**
     * 默认的空线性表
     */
    private Object[] DEFAULT_EMPTY_ELEMENTDATA={};
    /**
     * 定义一个List的最大容量
     */
    private int DEFAULT_MAX_CAPACITY=Integer.MAX_VALUE-8;

        3、定义构造方法,用于初始化容器的容量。一般提供两个构造方法,一个为空构造方法,一个提供一个长度参数。

    /**
     * 初始化一个长度为length的ArrayList,注意,这里的length和size无关
     * @param length
     */
    public ArrayList(int length) {
        elementData=new Object[length];
    }

    /**
     * 初始化一个空的ArrayList
     */
    public ArrayList() {
        elementData=DEFAULT_EMPTY_ELEMENTDATA;
    }

4、实现最简单的两个方法,size(),isempty()

    @Override
    public int size() {
        //返回的是元素的个数,而不是数组的长度,所以只有往List中添加了元素之后,size才会增加
        return size;
    }

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

5、实现add方法

在实现add方法之前,有一点需要注意。我们初始化ArrayList时,数组的长度是固定的。如果容器的size已经超过了初始化的数组的长度,就会引发数组索引越界异常。所以在添加之前,都应该去判断容器的size是否超过了数组的长度。如果size>=elementData.length,则需要对数组进行扩容。所以我们首先把数组扩容的方法实现,添加就变得容易了。关于数组扩容,参照java.util.ArrayList中提供的方法。实现代码如下:

    /**
     * @description : 数组扩容
     * @param:minCapacity:需要扩大的最小容量
     * @return:
     * @auther: zwh
     * @date: 2018/12/28 10:23
     */
    private void grow(int minCapacity){
        //现在的数组的长度
        int oldCapacity=elementData.length;
        //新数组的长度,在现有数组长度的基础上,在增加1/2。这样不至于增加太多,也不至于太少,导致增加次数过多
        int newCapacity=oldCapacity+(oldCapacity>>1);
        //如果新的数组的长度>目前需要的数组长度,则扩大现在需要的数组长度
        if(minCapacity>newCapacity){
            newCapacity=minCapacity;
        }
        //如果新的数组长度大于设定的最大值,则直接去Integer能表示的最大值
        if (newCapacity>DEFAULT_MAX_CAPACITY){
            newCapacity=Integer.MAX_VALUE;
        }
        //通过数组拷贝,将原数组进行扩容。
        elementData=Arrays.copyOf(elementData,newCapacity);
    }

其实这个grow方法还有可以优化的地方,暂时先不考虑。然后再做add,就比较容易了:

    @Override
    public void add(Object e) {
        if(size>=elementData.length){
            grow(size+1);
        }
        elementData[ size++]=e;
    }

6、实现其他带索引位置的add方法。实现了最普通的add方法之后,考虑实现复杂一些的add方法。第一个是add(int index,Object element)。在指定的位置添加一个新的元素。因为其基于数组来实现的原因,在中间添加元素是比较麻烦的一件事情。因为后面的元素全部需要移动一次位置。所以在本次实现中,我主要使用了数组拷贝。当然,也可以使用循环赋值的方式实现,对于初学者来说更容易理解。

    @Override
    public void add(int index, Object element) {
        if(index<0 || index>=size){
            throw new ArrayIndexOutOfBoundsException("IndexOutOfBoundsException! current                    index:"+index+",size:"+size);
        }
        Object[] newElementData=new Object[size+1];
        //将原来数组的index之前的元素复制到新数组
        System.arraycopy(elementData,0,newElementData,0,index);
        //将元素赋值给新数组的第index个元素
        newElementData[index]=element;
        //将原数组的index之后的元素复制到新数组的index元素之后。
        System.arraycopy(elementData,index,newElementData,index+1,size-index);
        //将原来的数组指向新的数组
        elementData = newElementData;
        size++;
    }

接下来addBefore,addAfter就比较容易了。先找到索引位置,调用add(index,element)就ok了。不必再去写同样的代码。

    @Override
    public void addBefore(Object obj, Object element) {
        for(int i=0;i<size;i++){
            if(elementData[i]==obj){
                add(i,element);
                return;
            }
        }
    }

    @Override
    public void addAfter(Object obj, Object element) {
        for(int i=0;i<size;i++){
            if(elementData[i]==obj){
                add(i+1,element);
                return;
            }
        }
    }

7、get方法实现:这个也比较简单了,这是线性表的优势,基于数组的底层实现,对于查询来说非常容易。通过数组的索引即可快速查找对应的元素。

    @Override
    public Object get(int index) {

       if(index<0 || index>=size){
            throw new ArrayIndexOutOfBoundsException("IndexOutOfBoundsException! current index:"+index+",size:"+size);
        }
        return elementData[index];
    }

8、remove方法实现:首先remove(int index)方法,通过索引删除某个元素。这个于add方法中带索引参数类似。需要删除后对后面的元素进行位置的移动。同样使用数组拷贝:注意添加时的size++,删除元素时的size--。

     public void remove(int i) {
        try {
            throwIndexOutOfBoundsException(i);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Object[] newElementData=new Object[size-1];
        /*复制数组,从原数组的下标为0的对象开始,复制长度为i,当i=0时,代表不复制,否则复制长度与i
         *相同下标为i的元素未复制,复制范围:elementData[0,i-1]*/
        System.arraycopy(elementData,0,newElementData,0,i);
        /*复制数组,从原数组的下标为i+1的对象开始,复制长度为size-i-1,下标范围:[i+1,size-1]*/
        System.arraycopy(elementData,i+1,newElementData,i,size-i-1);
        elementData=newElementData;
        size--;
    }

再实现remove的其他方法。就可以调用已经写过的方法了。统一通过索引去删除元素

    @Override
    public void remove(Object e) {
        for (int i=0;i<size;i++ ) {
            if (e==elementData[i]){
                remove(i);
                return;
            }
        }
    }

9、实现其他方法:剩下这些方法都很容易理解,就不多做描述了。

1)indexOf方法:通过遍历去找索引

     @Override
    public int indexOf(Object e) {
        for (int i=0;i<size;i++) {
            if(e==elementData[i]){
                return i;
            }
        }
        return -1;
    }

2)contains方法:一样是通过遍历。

    @Override
    public boolean contains(Object e) {
        for (Object obj:elementData) {
            if(e==obj){
                return true;
            }
        }
        return false;
    }

3)replace方法:真正开发中需要注意i越界的问题。

    @Override
    public Object replace(int i, Object e) {
        elementData[i]=e;
        return e;
    }

三、总结

线性表底层基于数组来实现,目前我们是统一使用Object来作为元素的类型,真正的ArrayList还需要使用泛型,通过泛型可以对元素的数据类型进行指定。

基于数组的底层实现,赋予了ArrayList查询的便利性,并且对于添加元素到最后,删除最后一个元素等方法都非常容易,但对于在List的中间部分添加元素,删除元素等,就显得比较复杂了。这和LinkedList是正好互补。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云间歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值