ArrayList源码基本实现和效率分析

本文深入分析ArrayList的底层原理,探讨其存储结构、成员属性及构造方法。详细讲解添加、删除、更改和查询元素的方法,并讨论了ArrayList在效率上的特点,指出在增删操作时可能存在的性能问题,但在查询方面表现出较高的效率。
摘要由CSDN通过智能技术生成

ArrayList源码基本实现和效率分析

1.ArrayList底层原理

1.1 存储结构

​ arraylist底层使用数组作为数据存储结构,使用一个Object[]数组来存储数据,当创建一个arraylist集合时,会创建一个Object[]数组,具体数据中存储的数据类型根据泛型进行约束,这里对源码进行一个基本功能的实现

1.2 成员属性
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
	底层数组的初始化默认容量
private static final Object[] EMPTY_ELEMENTDATA = {};
	当用户创建集合未设置容量时elemrntData的引用指向
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	当用户创建集合设置容量为0时elemrntData的引用指向
transient Object[] elementData;
	arrayList底层存储数据的数组
private int size;
	记录arrayList底层数组中元素的个数
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	数组结构扩容的最大范围,即数组中元素最大的个数
1.3 构造方法

ArrayList提供了三个构造方法来创建ARrayList集合

1.3.1ArrayList(int initialCapacity)
该方法要求用户传入初始化容量大小,用于分配空间
当传入容量为0时,elementData= EMPTY_ELEMENTDATA
当传入容量大于0时,elementData=new Object[initialCapacity]
1.3.2ArrayList()
该方法不要求用户传入参数,默认构建空数组elementData= DEFAULTCAPACITY_EMPTY_ELEMENTDATA 
1.3.3ArrayList(Collection<? extends E> c)
该方法可以将Collection对象转换为ArrayLIst集合,实现是调用了toArray() 和  Arrays.copyOf()方法

2.ArrayList方法

Collection是接口List的实现类,间接是Collection的实现类,在方法的命名上遵循List接口的命名规则,实现对集合【底层数组】中数据的增删改查的操作

2.1添加元素方法add

添加元素的方法有两个,一是添加元素到集合末尾,二是添加元素到指定下标位置

在源码中,对底层数组是否进行扩容操作进行了方法封装,这里没有进行,如果想要查看封装,请移步到源码进行查看

添加元素到指定下标位置实现,只需要将底层数组中指定下标包括之后元素向后移动一个位置,将元素插入即可

同时,插入操作需要对记录集合元素个数的成员属性size进行++操作

    /**
     * 添加元素到数组末尾的方法
     *
     * @param e 泛型约束的数据类型 要添加的元素
     * @return 返回添加状态  true代表成功
     */
    public boolean add(E e) {
        if (size == elementData.length) {//判断容量是否满足要求
            grow(size + 1);//扩容
        }
        elementData[size] = e;
        size++;
        return true;
    }

    /**
     * 添加元素到数组指定下标的方法
     *
     * @param index int类型  插入位置的下标
     * @param e     泛型数据类型 要添加的元素
     * @return 返回true 代表添加成功
     */
    public boolean add(int index, E e) {
        //index检查
        checkIndexAdd(index);
        if (size == elementData.length) {
            grow(size + 1);
        }
        for (int i = size; i > index; i--) {
            elementData[i] = elementData[i - 1];
        }
        elementData[index] = e;
        size++;
        return true;
    }
2.2删除元素方法remove

删除集合中元素,这里实现了源码中的两个方法:指定下标删除,指定元素删除寻找到的第一个

    /**
     * 删除指定下标元素
     *
     * @param index int类型 数组下标
     * @return 返回被删除的元素
     */
    public E remove(int index) {
        //检查下标-> 数据左移 -> 最后一位数据赋值空 -> size-1
        checkIndex(index);
        E oldValue = (E) elementData[index];
        for (int i = index; i < size; i++) {
            elementData[i] = elementData[i + 1];
        }
        elementData[size - 1] = null;
        return oldValue;
    }

    /**
     * 删除指定元素 从头遍历 只删除第一个指定元素
     *
     * @param obj 用户指定的数据  Object数据类型
     * @return 返回删除状态 返回false代表数据没有找到
     */
    public boolean remove(Object obj) {
        if (null == obj) {
            for (int i = 0; i < size; i++) {
                if (elementData[i] == null) {
                    remove(i);
                    return true;
                }
            }
        } else {
            /*
            使用了equals进行元素对比,针对于List中的对象,需要重写equals方法?
             */
            for (int i = 0; i < size; i++) {
                if (obj.equals(elementData[i])) {
                    remove(i);
                    return true;
                }
            }
        }
        return false;
    }
2.3更改元素方法set

更新方法,将更新前的元素返回给用户

    /**
     * 更新指定下标的元素
     *
     * @param index int数据类型 指定下标未知
     * @param e     泛型约束的数据类型 要更新的数据
     * @return 泛型约束的数据类型 返回修改前的数据
     */
    public E set(int index, E e) {
        checkIndex(index);
        E oldValue = (E) elementData[index];
        elementData[index] = e;
        return oldValue;
    }
2.4查询元素方法

这里实现了五个源码中的方法

 /**
     * 获取指定下标元素的方法
     *
     * @param index int数据类型 指定下标
     * @return E泛型约束类型  返回指定下标未知的元素
     */
    public E get(int index) {
        checkIndex(index);
        return (E) elementData[index];
    }

    /**
     * 返回指定元素的第一个下标
     *
     * @param obj Object 指定元素
     * @return int数据类型  元素下标位置 返回-1代表数据不存在
     */
    public int indexOf(Object obj) {
        if (obj == null) {
            for (int i = 0; i <size ; i++) {
                if (elementData[i] == null){
                    return i;
                }
            }
        } else {
            for (int i = 0; i <size ; i++) {
                if (obj.equals(elementData[i])){
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * 寻找指定元素的最后一个下标
     * @param obj Object 指定元素
     * @return int数据类型  元素下标位置 返回-1代表数据未找到
     */
    public int lastIndexOf(Object obj) {
        if (obj == null) {
            for (int i = size-1; i >= 0  ; i--) {
                if (elementData[i] == null){
                    return i;
                }
            }
        } else {
            for (int i = size-1; i >= 0  ; i--) {
                if (obj.equals(elementData[i])){
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * 判断指定元素是否存在集合中
     * @param obj Object 指定元素
     * @return Boolean  返回true存在 返回false不存在
     */
    public boolean contains(Object obj) {
        return indexOf(obj) > 0;
    }

    /**
     * 集合转换成Object数组
     * @return Object[]
     */
    public Object[] toArray() {
        return Arrays.copyOf(elementData,size);
    }

    /**
     * 从指定下标开始截取新集合到指定下标
     * @param start int 开始下标
     * @param end int 截至下标
     * @return List  截取后的集合
     */
    public List<E> subList(int start, int end) {
        checkIndexSubList(start,end);
        ArrayList<E> newArrayList = new ArrayList<>();
        newArrayList.elementData = Arrays.copyOfRange(elementData,start,end);
        return (List<E>) newArrayList;
    }

2.5其他私有方法

在以上增删改查的方法中,需要调用一些私有的封装方法来完成操作,例如:在增加、删除、修改、查询指定下标元素、截取指定集合时,对用户传入下标的验证操作、当底层数组容量不足时的扩容操作,

2.5.1检查下标方法
    /**
     * 检查截取集合方法的下标是否合法
     * @param start 开始下标
     * @param end 结束下标
     */
    private void checkIndexSubList(int start, int end) {
        if (start > end){
            throw new IndexOutOfBoundsException("您输入的下标不正确 start > end" );
        }
        checkIndex(start);
        checkIndex(end);
    }
    
        /**
     * 检查添加元素时传入的下标是否合法  当下标小于0时不合法
     *
     * @param index int数据类型
     */
    public void checkIndexAdd(int index) {
        if (index < 0) {
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }

    /**
     * 检查元素下标是否合法  当大于size 或者小于0 时不合法 用户删改查时对输入下标的判断
     *
     * @param index int类型
     */
    public void checkIndex(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }


    /**
     * 当对下标进行检查后下标不合法时抛出IndexOutOfBoundsException异常时的message信息
     *
     * @param index int类型 数组下标
     * @return String类型 msg提示信息
     */
    public String outOfBoundsMsg(int index) {
        return "下标不合法:" + index + "当前size为:" + size;
    }
2.5.3扩容方法
    /**
     * 扩容方法
     *
     * @param minCapacity 数组的最小容量
     */
    private void grow(int minCapacity) {
        //获取新数组容量 ->判断新数组容量是否满足最小容量要求 ->判断新数组容量是否超出最大容量 ->创建新数组->数据拷贝->转移引用
        int newCapacity = elementData.length + elementData.length / 2;
        if (minCapacity > MAX_ARRAY_SIZE) {
            throw new IllegalArgumentException("无法进行扩容,容量已经达到最大:" + MAX_ARRAY_SIZE);
        }
        if (newCapacity < minCapacity) {
            newCapacity = minCapacity;
        }
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
2.6tips

在以上代码中,使用了Arrays工具类的一些方法,这里不做解释,请自行查找资料

4.ArrayList效率分析

由于ArrayList集合底层使用了数组作为数据存储结构,同时也继承了数组的优点和缺点

向集合中增加元素时,需要对底层数组的容量进行判断,当容量不足时需要调用扩容方法进行扩容的操作,在扩容时数据的复制需要耗费较长时间;指定下标位置插入数据的话,又需要将指定下标位置往后元素整体向右移动,需要耗费较长时间。

当批量删除指定下标元素时,需要将指定下标后方数据整体左移,耗费较长时间;当批量删除时,会造成底层数组空间冗余的问题。

查询时,由于底层时使用数组作为存储结构,每个元素都在内存中有自己的"空间地址",使用数组+下标的方式进行寻址操作效率较高。

因此,ArrayList集合,在增加或者删除元素时的效率相对较低,在查询元素时的效率相对较高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值