ArrayList源码分析

  • 底层实现是数组(没啥好说的这个都知道)

成员变量

   /**
     *  默认容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空数组,如果传入的容量为0时使用
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 空数组,传传入容量时使用,添加第一个元素的时候会重新初始为默认容量大小
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 存储元素的数组
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList的最大值
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 集合中元素的个数
     */
    private int size;

构造方法

   /**
     *   带有初始容量的构造方法
     */
    public ArrayList(int initialCapacity) {

        // 按照初始值创建集合的大小
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        // 传入0用null的
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
        }
    }


    /**
     *  直接创建原来的数组
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }


    /**
     *  传入集合的
     */
    public ArrayList(Collection<? extends E> c) {

        elementData = c.toArray();

        // 如果不是null数组
        if ((size = elementData.length) != 0) {

            // 检查c.toArray()返回的是不是Object[]类型,如果不是,重新拷贝成Object[].class类型  这就代表泛型没啥用
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);

        //如果是null数组
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

添加元素

    /**
     * 添加元素
     */
    public boolean add(E e) {
        // 修改次数加一  用于快速失败机制 什么是快速失败
        modCount++;
        // 添加元素
        add(e, elementData, size);
        return true;
    }
    /**
     *  添加元素
     *  e:要添加的元素
     *  elementData:当前存放的数组的大小
     *  s:当前数组的长度
     */
    private void add(E e, Object[] elementData, int s) {

        // 如果当前数size的大小等于数组的长度
        if (s == elementData.length)
            // 扩容
            elementData = grow();

        // 设置size的位置是当前元素
        elementData[s] = e;
        //把size+1
        size = s + 1;
    }
  • 总结:
  • (1) 如果当前size的大小等于数组的长度 进行扩容处理
  • (2) 并且设置,并且赋值         数组[数组当前长度] = 要添加的值  (巧妙运用了数组的长度 跟 坐标的关系)
  • (3) 把当前size值减1

按照指定位置添加元素

    public void add(int index, E element) {
        /*越界检查*/
        rangeCheckForAdd(index);
        modCount++;
        final int s;
        Object[] elementData;
        /*检查是否扩容*/
        if ((s = size) == (elementData = this.elementData).length)
        elementData = grow();

        /*将index及其之后的元素往后挪一位,则index位置处就空出来了*/
        System.arraycopy(elementData, index, elementData, index + 1, s - index);

        /*设置当前index的元素*/
        elementData[index] = element;
        size = s + 1;
    }

好玩的来了嘿嘿!角标越界方法

    /**
     * 越界检查
     */
    private void rangeCheckForAdd(int index) {
        /*size 是当前元素的数量 也就是说按照索引设置值 
        最多只能放到已经有的元素个数的位置*/
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
  • 也就是说按照索引存放元素的时候      下标只能取小于等于元素数量的值(也就是)  设置的下标一定要小于等于size否则会报角标越界异常算不算是一个坑呢?哈哈
  • 例子1

  • 运行结果

扩容grow()

    /**
     * 扩容往下走
     * @return
     */
    private Object[] grow() {
        // 扩容当前size+1
        return grow(size + 1);
    }
  • 总结:
  • 因为要增加一个元素,所以说当前数组的长度+1(这个时候还没增加)
    /**
     * 扩容往下走
     * @param minCapacity
     * @return
     */
    private Object[] grow(int minCapacity) {
        // Arrays.copyOf 创建新数组
        return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
    }
  • 这里运用了Arrays.copyOf方法copy出一个新的数组 并且通过 newCapacity(minCapacity) 来计算新数组的长度
    /**
     * 真正扩容的方法
     * @return
     */
    private int newCapacity(int minCapacity) {
        // 老的数组的长度
        int oldCapacity = elementData.length;

        // 薪的数组的长度=老数组的长度+老数组的长度/2
        int newCapacity = oldCapacity + (oldCapacity >> 1);

        // 如果扩容之后还是不满足  什么情况之下会不足?
        // 第一种 初始化 oldCapacity 是0
        // 第二种 putAll 放了一个集合但是它放置的长度比较大
        if (newCapacity - minCapacity <= 0) {

            // 如果数组没有被初始化过
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)

                // 返回默认容量和要扩充的空间中的的最大值
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();

            // 如果不是初始化 而且容量不足 那么就返回他要扩容的这个大小
            // 1 记住去了问面试官这个问题 这块就证明 Arraylist的集合的容量 不一定是5的倍数
            return minCapacity;
        }

        //返回薪数组的长度
        return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity :         
        hugeCapacity(minCapacity);
    }
  • 总结
  • (1)  新数组的长度=老数组的长度+老数组的长度/2 也就是扩容1.5倍
  • (2)  如果扩容之后还是不满足  什么情况之下会不足?
  •        第一种 初始化 oldCapacity 是0或者putAll 放了一个集合但是它放置的长度比较大
  •       (2-1)如果没经过初始化 这里分放置一个元素 和放置多个元素(putAll) 所以就取(默认容量10 与 要扩容的大小的最大值) 返回这个长度作为新数组的长度
  •     (2-2)如果经历了初始化但是扩容之后的容量还是放不下,那么就取这个最大值 ,返回这个长度作为新数组的长度
  •                 这里就表明虽然说默认是10  扩容为1.5 倍 但是容量不一定是5的倍数 因为putAll操作存在扩容完毕之后存不下直               接取当前要扩容的值的情况
  • (3)扩容之后能满足,返回扩容之后的容量作为新数组的长度

一次性添加多个元素addAll()方法

    /**
     * 一次性添加多个元素
     * @param c
     * @return
     */
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        modCount++;
        // 取出薪数组的长度
        int numNew = a.length;
        if (numNew == 0)
            return false;
        Object[] elementData;
        final int s;

        // 如果要放置的元素 大于目前数组还闲置的位置
        if (numNew > (elementData = this.elementData).length - (s = size))

            // 扩容
            elementData = grow(s + numNew);

        // arraycopy 创建新数组
        System.arraycopy(a, 0, elementData, s, numNew);

        //  设置当前元素的数量
        size = s + numNew;
        return true;
    }
  • 跟put方法差不多有差别的地方有三点
  • (1) 根据   如果要放置的元素 大于目前数组还闲置的位置来判断是够扩容
  • (2) 扩容的数量不一致
  • (3) 多了用 System.arraycopy copy的是两个数组来放置要放置的元素集 

通过索引获取元素

    /**
     *   通过索引获取元素中的位置
     */
    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }


    /**
     *  通过索引获取元素中的位置
     */
    public E get(int index) {
        return elementData(index);
    }

    /**
     *  按照索引位置返回旧值
     */
    public E set(int index, E element) {
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
  • 比较简单没啥可说的

通过索引删除元素

    /**
     * 按照索引删除的话就直接删除就好了 并且返回旧的值
     */
    public E remove(int index) {
        final Object[] es = elementData;
        @SuppressWarnings("unchecked") E oldValue = (E) es[index];
        fastRemove(es, index);
        return oldValue;
    }
    /**
     *    删除元素
     */
    private void fastRemove(Object[] es, int i) {
        modCount++;
        final int newSize;
        // 如果index不是最后一位 则将index之后的元素向前挪一位
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);

        // 将最后一个元素至null
        es[size = newSize] = null;
    }
  • 总结:删除元素的核心就是   System.arraycopy(es, i + 1, es, i, newSize - i);的删除操作
  •  如果index不是最后一位 则将index之后的所有元素向前挪一位,如果是最后一位直接置null

通过元素删除集合中元素

    /**
     *  按照元素进行删除操作
     */
    public boolean remove(Object o) {

        final Object[] es = elementData;

        final int size = this.size;

        int i = 0;

        /**
         * 循环遍历确定元素索引的位置
         */
        found: {
            if (o == null) {
                for (; i < size; i++)
                    if (es[i] == null)
                        break found;
            } else {
                for (; i < size; i++)
                    if (o.equals(es[i]))
                        break found;
            }
            return false;
        }

        /**
         * 按照索引去删除元素
         */
        fastRemove(es, i);
        return true;
    }
  • 总结:
  • (1)  循环遍历确定索引的位置
  • (2)  再通过索引去删除元素

关于ArrayList的一些总结

  • (1)按照索引位置进行访问效率很高
  •  (2)添加元素的效率还算可以(扩容采用copyof效率也还可以)
  •  (3)按照索引删除元素效率比较低 因为要移动元素
  •  (4) 按照元素内容删除元素效率更低,因为要确定索引的位置,然后再通过索引删除元素,之后再移动元素
  •  (5) 按照索引位置添加元素,效率比较低因为要移动元素,还tmmp的有个坑

一声坏笑下次我要问面试官俩问题

  1. 既然默认的容量是10,每次扩容按照1.5 ,那集合容量第一次扩容的大小就一定是5的倍数15嘛?
  2. ArrayList<Integer> arrrrayList = new ArrayList();
    arrrrayList.add(1,1);会发生啥?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值