手把手带你实现一个MyArrayList(动态数组)

作者简介:☕️大家好,我是intelligent_M,一个Java后端开发者!
当前专栏:intelligent_M—— 实现一个MyArrayList ,CSDN博客。

后续会更新Java相关技术栈。
创作不易 欢迎点赞评论!!!


自己实现一个ArrayList

几个关键点
关键点一、自动扩缩容
在实际使用动态数组时不仅需要扩容,缩容也是重要的优化手段。比方说一个动态数组开辟了能够存储 1000 个元素的连续内存空间,但是实际只存了 10 个元素,那就有 990 个空间是空闲的。为了避免资源浪费,我们其实可以适当缩小存储空间,这就是缩容。
我们这里就实现一个简单的扩缩容的策略:
当数组元素个数达到底层静态数组的容量上限时,扩容为原来的 2 倍;
当数组元素个数缩减到底层静态数组的容量的 1/4 时,缩容为原来的 1/2。

关键点二、索引越界的检查
下面的代码实现中,有两个检查越界的方法,分别是 checkElementIndex 和 checkPositionIndex,你可以看到它俩的区别仅仅在于 index < size 和 index <= size。
为什么 checkPositionIndex 可以允许 index == size 呢,因为这个 checkPositionIndex 是专门用来处理在数组中插入元素的情况。
比方说有这样一个 nums 数组,对于每个元素来说,合法的索引一定是 index < size:
nums = [5, 6, 7, 8]
index 0 1 2 3
但如果是要在数组中插入新元素,那么新元素可能的插入位置并不是元素的索引,而是索引之间的空隙:
nums = [ | 5 | 6 | 7 | 8 | ]
index 0 1 2 3 4
这些空隙都是合法的插入位置,所以说 index == size 也是合法的。这就是 checkPositionIndex 和 checkElementIndex 的区别。

关键点三、删除元素谨防内存泄漏
单从算法的角度,其实并不需要关心被删掉的元素应该如何处理,但是具体到代码实现,我们需要注意可能出现的内存泄漏。
在我给出的代码实现中,删除元素时,我都会把被删除的元素置为 null,以 Java 为例:
// 删
public E removeLast() {
E deletedVal = data[size - 1];
// 删除最后一个元素
// 必须给最后一个元素置为 null,否则会内存泄漏
data[size - 1] = null;
size–;
return deletedVal;
}
Java 的垃圾回收机制是基于引用计数的,如果一个对象没有被引用,那么这个对象占用的内存才会被释放;否则,垃圾回收器会认为这个对象还在使用中,就不会释放这个对象占用的内存。
如果你不执行 data[size - 1] = null 这行代码,那么 data[size - 1] 这个引用就会一直存在,这个对象的内存就一直不会被释放,进而造成内存泄漏。
其他带垃圾回收功能的语言应该也是类似的,你可以具体了解一下你使用的编程语言的垃圾回收机制,这是写出无 bug 代码的基本要求。

  • 代码展示
import java.util.Arrays;
import java.util.NoSuchElementException;

/**
 * @author M
 * @version 1.0
 * 动态数组
 */
public class MyArrayList<E> {
    //真正存储数据的底层数组
    private E[] data;
    //记录当前元素个数
    private int size;
    //默认初始容量
    private static final int INIT_CAP = 1;

    public MyArrayList(){
        this(INIT_CAP);
    }

    public MyArrayList(int initCapacity){
        data = (E[]) new Object[initCapacity];
        size = 0;
    }

    //新增 在尾部添加元素
    public void addLast(E e){
        int cap = data.length;
        //看data数组容量够不够
        if(size == cap){
            resize(2 * cap);//进行扩容两倍
        }
        //在尾部插入元素
        data[size] = e;
        size++;
    }

    //指定位置添加元素
    public void add(int index, E e){
        //检查索引越界
        checkPositionIndex(index);

        int cap = data.length;
        //看data 数组容量够不够
        if(size == cap){
            resize(2 * cap);
        }

        //搬移元素 data[index..] -> data[index + 1...]
        //给新元素腾出位置(倒序遍历防止元素被覆盖)
        for(int i = size - 1; i >= index; i--){
            data[i + 1]  = data[i];
        }
        //插入新元素
        data[index] = e;
        size++;
     }

     //从头部添加元素
    public void addFirst(E e){
        add(0,e);
    }

    //删除尾部最后一个元素 并返回
    public E removeLast(){
        if(size == 0){
            throw new NoSuchElementException();
        }
        int cap = data.length;
        //可以缩容,节约空间
        //判断size数组的元素个数 是否 等于数组容量的1/4 是就缩容
        if( size == cap/4){
            resize(cap / 2);
        }
        E deletedVal = data[size -1];
        //删除最后一个元素
        //必须给最后一个元素置为null,否则会内存泄露
        data[size - 1] = null;
        size--;
        //并返回删除元素的值
        return deletedVal;
    }

    //根据索引删除值,并返回被删除值
    public E remove(int index){
        //检查索引越界
        checkElementIndex(index);

        int cap = data.length;//数组长度
        //判断是否可以缩容
        if(size == cap/4){
            resize(cap / 2);
        }

        E deletedVal = data[index];

        //搬移元素 data[index+1...] -> data[index...]
        for(int  i = index + 1; i < size; i++){
            data[i - 1] = data[i];
        }
        //如果不置为null 这个对象的内存就不会被释放 进而造成 内存泄漏
        data[size -1] = null;//防止内存泄漏
        size--;

        return deletedVal;
    }

    //根据索引查询元素
    public  E get(int index){
        //检查索引越绝
        checkElementIndex(index);

        return data[index];
    }

    //修改指定索引的值 并返回旧值
    public E set(int index, E e){
        //检查索引越界
        checkElementIndex(index);
        //修改数据
        E oldVal = data[index];
        data[index] = e;
        return oldVal;
    }

     //*******************工具方法*****************/
    //返回动态数组的大小
    public int size(){
        return size;
    }

    //判断数组是否为空
    public boolean isEmpty(){
        return size == 0;
    }

    //扩容or 缩容  将data的容量改为newCap
    private void resize(int newCap){
        //判断 元素个数 大于 新的容量 如果是 返回不能缩容
        if(size > newCap){
            return ;
        }
        //扩容 new 一个新扩容后的数组
        E[] temp = (E[]) new Object[newCap];
        //将 data里的元素 复制给 新数组 (这个可以用System.arrayCopy()方法来拷贝数组)
        for(int i = 0 ; i < size; i++){
            temp[i] = data[i];
        }
        //更新引用
        data = temp;
    }

    //判断是否包含这个索引
    private boolean isElementIndex(int index){
        return index >= 0 && index < size;
    }

    //判断 是否可以在这个index 插入元素
    private boolean isPositionIndex(int index){
        return index >= 0 && index <= size;
    }

    /**
     * 检查 index 索引位置是否可以存在元素
     */
    private void checkElementIndex(int index){
        if(!isPositionIndex(index))
            throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
    }

    /**
     * 检查index 索引位置是否可以添加元素
     */
    private void checkPositionIndex(int index){
        if(!isPositionIndex(index))
            throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
    }

    //显示 size大小  和元素个数    和元素
    private void display(){
        System.out.println("size = " + size + "\ncap = " + data.length);
        System.out.println(Arrays.toString(data));
    }

    //********************测试*********************//
    public static void main(String[] args) {
        //初始容量设置为3
        MyArrayList<Integer> list = new MyArrayList<>(3);
        //添加5个元素
        for(int i = 1; i <= 5; i++){
            list.addLast(i);
        }
        list.display();
        System.out.println("**********");

        list.remove(3);
        list.add(1,9);
        list.addFirst(100);
        int val = list.removeLast();


        for (int i = 0; i < list.size(); i++){
            System.out.println(list.get(i));
        }
    }
}

  • 实际上你还可以对上述代码做一些优化
  • 比如数组的拷贝可以用System.arraycopy()
  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Intelligent_M

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

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

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

打赏作者

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

抵扣说明:

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

余额充值