数据存储——实现ArrayList

数据存储——实现ArrayList

本章节的源代码位于gitee上,想要下载的请点击实现ArrayList

ArrayList简介

Java提供了数据存储的集合List,主要用于存储单值。在List集合中有两个常用的数据存储类,一个是LinkedList,它是以链表的方式进行存储的,关于链表的相关知识点可以去查看我这两个博客数据存储——双向链表数据存储——单向链表。还有一个就是ArrayList,这个类在开发中的使用占比达90%以上,与LinkedList不同的是,ArrayList使用的是数组进行数据存储。它的有点在于可以直接通过数组下标查询到数据,可以说在查询上的时间复杂度为O(1)。因此基于这个特点,这一章节来详细讲讲。通过我们自己手写的方式来模拟ArrayList。

ArrayList实现

ArrayList作为数据存储的类,一定会提供增删改查的方法。但是首先需要说明的一点是,由于它是基于数组的方式来完成数据存储的,那么我们就需要开辟数组,而不能像LinkedList一样,使用一次在创建一次。同时需要考虑的问题就是如果数组开辟的空间不足,该如何扩容的问题。那么我们就从创建ArrayList开始吧。

创建ArrayList与存储设计

由于在面试中常被问到ArrayList,所以这一次设计就完全按照ArrayList的源码来。首先看看Java提供的ArrayList在实例化的时候都做了些什么。

//有参构造
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    }
}
//无参构造
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

这里就只讨论这两种方式,当前还有ArrayList(Collection<? extends E> c)这里就先不提。如果有兴趣的朋友可以自行去查询,原理类似,都是通过传入数据的大小进行开辟数组空间。上述的两个构造方法中有一些默认值,它们分别是

private static final Object[] EMPTY_ELEMENTDATA = {};//空数组
transient Object[] elementData;//使用中的数组

这是我们就有一个疑问,默认的是一个空数组啊,我们要存储数据该怎么存储呢?接下来就简单看看添加数据是如何操作的吧,看看有没有操作数组的方法。

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}
private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}
private Object[] grow() {
    return grow(size + 1);
}
private Object[] grow(int minCapacity) {
    return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
}
private int newCapacity(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);//数组扩充
    if (newCapacity - minCapacity <= 0) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            return Math.max(DEFAULT_CAPACITY, minCapacity);//数组初始化
        if (minCapacity < 0) 
            throw new OutOfMemoryError();
        return minCapacity;
    }
    return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity);
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private static final int DEFAULT_CAPACITY = 10;

这个时候我们就可以看到这个数组的初始化是在什么时候开始的了,通过添加数据,然后判断数组为空,于是实例化一个长度为10的数组。这里主要的判断依据在于数组长度>>1,因为1>>1还是为0,所有就有了下面的return Math.max(DEFAULT_CAPACITY, minCapacity);因此,到这里我们可以知道了,ArrayList的默认大小为10,每次扩充为原来数组长度的50%。

看上述的一些代码我们可能会有些难懂,接下来我将自己设计一个ArrayList,通过我的代码,然后再去看可能会有更好的理解。我的代码可能会和源代码有所不同,请注意。重点是了解ArrayList的原理和相关的思想。

interface IList<E>{
    boolean add(E e);
}
class ArrayListImpl<E> implements IList<E>{
    private Object elementData[];//数组
    private int listLength;//数组中数据长度
    private final int DEFAULTLENGTH = 10;
    //无参构造
    public ArrayListImpl(){
        this(0);
    }
    public ArrayListImpl(int size){
        if(size>=0){
            this.elementData = new Object[size];
        } else {
            throw new IllegalArgumentException("你输入的数据错误"+size);
        }
    }
    private Object[] newCapacity(E e){
        if(this.elementData.length==0){
            this.elementData = new Object[this.DEFAULTLENGTH];//初始化默认值
        } else {
            int oldCapacity = this.elementData.length;//获取数据原始长度
            int newCapacity = oldCapacity>>1;//增加的长度
            this.elementData = Arrays.copyOf(elementData,oldCapacity+newCapacity);//扩容
        }
        this.elementData[this.listLength++] = e;//添加数据
        return this.elementData;
    }
    @Override
    public boolean add(E e) {
        if(this.elementData.length==this.listLength){//数组已经满载了,需要开辟新的长度
            newCapacity(e);
        } else {
            this.elementData[this.listLength++] = e;//添加数据
        }
        return true;
    }
}

在我自己写的这个类中,将大量的操作进行了简化,方便初学者理解。

数据读取

在ArrayList之中是没有提供数据输出的方法有很多种,如:toArray()、get(int index)、iterator()等。首先我们还是看看源代码中对这些方法是如何实现的。

public E get(int index) {
    Objects.checkIndex(index, size);//检查 index是否在 0 (含)到size(不包括)范围内。
    return elementData(index);//返回结果
}
public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

这两个没有什么可以说的,主要来看看iterator()这个方法。如果感觉看这个源代码有点难懂的可以直接跳过这段代码,直接看我是如何实现的即可,用最简单的放来实现。

public Iterator<E> iterator() {
    return new Itr();
}
private class Itr implements Iterator<E> {
    int cursor; 
    int lastRet = -1; 
    int expectedModCount = modCount;
    Itr() {}
    public boolean hasNext() {
        return cursor != size;
    }
    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }
    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
    @Override
    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i < size) {
            final Object[] es = elementData;
            if (i >= es.length)
                throw new ConcurrentModificationException();
            for (; i < size && modCount == expectedModCount; i++)
                action.accept(elementAt(es, i));
            // update once at end to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }
    }
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

自定义iterator()方法,该方法需要一个类继承Iterator接口,并实现该接口中的两个方法,如果你想实现四个也是可以的。上述代码就实现了四个。

public Iterator<E> iterator() {
    return new Ite();
}
private class Ite implements Iterator{
    int len=-1;
    @Override
    public boolean hasNext() {
        return this.len != listLength;
    }
    @Override
    public Object next() {
        if(len<listLength){
            return elementData[++len];
        }
        return null;
    }
}

该代码的简洁度更高,更加利于理解,初学者适合看这种代码,而想要深入理解的可以直接看源码。

进行测试

public class SimulateArrayList {
    public static void main(String[] args) {
        IList<String> list = new ArrayListImpl<>();
        list.add("A");
        list.add("B");
        list.add("C");
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6P3ujmvS-1601964130367)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201006135605844.png)]

至于修改和删除数据,这里就不在细说了,原理类似,有兴趣的朋友可以直接查看源码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值