带你手写ArrayList

原文地址https://www.cnblogs.com/hkblogs/p/9025221.html ,欢迎转载!

前言

这是带给大家的第一篇技术文章,也是所有的java开发人员肯定使用过的。因为相比较其它数据结构而言,无疑它是最简单的。所以今天放第一篇来给大家介绍。并自行实现一个简易版的ArrayList。

本文借鉴jdk7与8实现,部分变量命名参考jdk命名,细节无法顾全,但大体思想是jdk的思想,做一个简化版来帮助大家理解。

关注点

不只是ArrayList,在任何集合中,都应关注以下几点:

ArrayList

是否可以为空

可以为空

是否有序(插入时候的顺序和读取时是否一致)

有序

是否允许重复

允许

是否线程安全

特性

 读取快,删除与插入慢

 

 

 

 

 

 

 

 

 

 

 

原理

ArrayList底层通过数组实现,在Java中即Object[]  对象,数组是可以存放大量数据的,所以定义一个Object数组,我们可以想放任何我们要存储的数据。

实现

既然要使用数组存放数据,首先定义一个Object数组,用来存放数据。并定义size属性来标识list的内容的长度。jdk源码是一个泛型类,简易版为了看的方便未加入泛型。(仅需定义类中定义泛型,返回值改为对应泛型,关于泛型的详细讲解,会在后续章节讲到)

public class HkArrayList {
    private  Object[] elementData;//存放数组变量
    private int size;//list存放的内容长度

    public int size(){
      return size;
    }

}

 

定义好之后,就要创建Arraylist对象,给一个默认无参构造器。

    //构造器
    public HkArrayList(){
        this.elementData=new Object[10];
    }

此处,给定数组默认大小为10(当然jdk不是这样写的构造器,jdk中默认长度是10,可以自行传参指定长度),那么假如添加11个元素怎么办?是的,肯定是ArrayIndexOutOfBoundsException,那么怎么解决此下标越界,请往下看!

接下来,我们实现第一个方法,添加元素add()方法,很简单:

    public boolean add(Object o){
        elementData[size++]=o;
        return true;
    }

因为是顺序添加的,假设第1次调用add("aaa")时,此时size=0,则为:elementData[0]="aaa",因为size++自增,以此保证后续调用正常,此处应该很好理解。

测试

        HkArrayList list =new HkArrayList();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        System.out.println(list.size()); //3

此处肯定是打印3,此时,用图表示,大概是这样的:

那么,问题来了,假如一直往上增,超过了数组的10个长度怎么办?此时,需要动态对数组进行扩容:

事实上,在java中,数组是没法进行扩容的,即第一次定义了长度后就不可改变了,否则怎么会还有下标越界这种异常呢?那么,我们要想对数组进行容量增大:

定义一个新的更大容量的数组,将原来的旧数组的内容拷贝进去。是的,就是这么简单!拷贝数组可用Arrays.copyOf()方法

新容量的大小,在jdk6中,是旧容量的*3/2+1,在jdk7与8中,是*3/2即1.5倍,只不过jdk为了效率和装X用的是移位算法。

所以对add方法改进如下:

1     public boolean add(Object o){
2         if ((size+1) - elementData.length > 0){
3             int oldCapacity=elementData.length;
4             int newCapacity=(oldCapacity*3)/2;
5             elementData=Arrays.copyOf(elementData, newCapacity);
6         }
7         elementData[size++]=o;
8         return true;
9     }

解释:第2行,判断是否需要扩容,如果当前的元素数再+1个元素>数组的长度,则需要扩容

第3-4行,定义新容量。第5行,将旧数组的全部内容拷贝到新数组中。OK,此时add方法应该没有问题了。

获取元素,get()方法比较简单

 1     public Object get(int index) {
 2         rangeCheck(index);
 3         return elementData[index];
 4     }
 5     
 6     //检查给定下标是否符合规则
 7     private void rangeCheck(int index){
 8         if(index>size||index<0){
 9             throw new IndexOutOfBoundsException("下标有误,Index:"+index+",size:"+size);
10         }
11     }

首先对于给定下标要做检查,鉴于集合中多处要有检查下标的地方,单独提取出此方法rangCheck(),此处不进行测试了,自行测试。

set方法也较为简单

1     private Object set(int index,Object element) {
2         rangeCheck(index);
3         Object oldValue = elementData[index];
4         elementData[index] = element;
5         return oldValue;
6     }

根据下标,返回值是旧的值,将旧值替换为新的值。

插入

ArrayList中,删除操作与随机插入操作的原理基本相似。先写一下插入一个元素在ArrayList中的实现

插入相对于顺序添加与修改和查询而言,稍复杂一些。

在jdk源码中,执行以下操作(此处讲解是jdk的Arraylist执行操作,并不是我写的HkArrayList)

1 list.add("aaa");
2 list.add("bbb");
3 list.add("ccc");
4 list.add(1,"new");

 

此时,1-3行执行完后,大概意思还是这样

当第4行在下标为1的位置插入执行后,变成了这样

画图是为了更好的说明,当然在内存中并不是这样放的。可以看到,下标1之前的没有动,下标1之后的元素,都向后移动了一位。

但是,java中的数组中可以这样操作吗?答案是当然不可以。

此时唯一的做法是数组复制。

调用System.arraycopy(Object src,int srcPos,Object dest,int destPos,int length)此方法第1个参数是源数组,第2个参数是源数组要复制的起始位置,

第3个参数是目标数组,第4个参数是目标数组放置的起始位置,最后一个参数是要复制的长度。

当源数组与目标数组相同时,即可实现我们的插入需求。好了,理论讲完开始代码:

1     public boolean add(int index,Object o){
2         rangeCheck(index);
3         ensureCapacityInternal(size+1);
4         System.arraycopy(elementData, index, elementData, index+1, size-index);
5         elementData[index]=o;
6         size++;
7         return true;
8     }

第2-3行是判断下标是否越界和是否需要扩容。对于是否需要扩容,多出用到,所以此处单独提取出get中检测是否需要扩容的方法,命名参考jdk8,

第4行即进行数组的拷贝,以刚才的例子:

第4行执行完后是这样的,1位置是空的,将1位置后边的所有元素拷贝到新数组中。

然后执行第5行,对1位置进行复制。

OK,大功告成,删除操作与此极为相似,大家可自行实现

总结

1、ArrayList查找方法get()速度非常快,因为只是从给定下标处拿一个值而已。

2、ArrayList顺序添加的速度也很快,因为只是在数组上给定位置放一个元素而已。(忽略容量不够扩容的影响)

3、ArrayList随机插入的时,性能较差,假如有10000个元素,在第2个位置插入一个元素,那么需要将后9998个元素做一次复制,如果业务中有大量的插入操作,慎用ArrayList。(下标删除操作与插入同理,较为耗性能)

4、网上的很多说法是不准确的,比如插入多用LinkedList,查询用ArrayList等等,其实不然,假如你确定你就是顺序插入,或你是靠末尾插入,此时ArrayList性能明显高于LinkedList(linkedList需要寻址,在下一章节为大家讲到)

疑问

关于大家的疑问,我会整理到这里,此处持续更新!

1、假如扩容多次后,做了大量删除,导致占用内存过大,此时做如何操作?

2、jdk源码中modCount是干嘛的?

附上源码

public class HkArrayList {
    private  Object[] elementData;
    private int size;
    
    //构造器
    public HkArrayList(){
        this.elementData=new Object[10];
    }
    
    public int size(){
        return size;
    }
    
    public boolean add(Object o){
        ensureCapacityInternal(size+1);
        elementData[size++]=o;
        return true;
    }
    
    //重载add方法通过下标来添加
    public boolean add(int index,Object o){
        rangeCheck(index);
        ensureCapacityInternal(size+1);
        System.arraycopy(elementData, index, elementData, index+1, size-index);
        elementData[index]=o;
        size++;
        return true;
    }
    
    
    public Object get(int index) {
        rangeCheck(index);
        return elementData[index];
    }
    
    //检查给定下标是否符合规则
    private void rangeCheck(int index){
        if(index>size||index<0){
            throw new IndexOutOfBoundsException("下标有误,Index:"+index+",size:"+size);
        }
    }
    
    public Object set(int index,Object element) {
        rangeCheck(index);
        Object oldValue = elementData[index];
        elementData[index] = element;
        return oldValue;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        if (minCapacity - elementData.length > 0){
            //需要的最小容量已经大于了数组的长度,此时需要扩容
            int oldCapacity=elementData.length;
            int newCapacity=(oldCapacity*3)/2;
            //如果扩容1.5倍还不够,则将传入的长度为新长度
            if (newCapacity - minCapacity < 0)
                newCapacity=minCapacity;
            elementData=Arrays.copyOf(elementData, newCapacity);
        }
            
    }

    @Override
    public String toString() {
        return "HkArrayList [elementData=" + Arrays.toString(elementData) + ", size=" + size + "]";
    }
    
    
    
    

}
View Code

 

posted @ 2018-05-11 16:08  HK博客  阅读(333)  评论(0)    收藏  举报