美文网首页周期计划Android开发经验谈Android开发
(周期计划-7)常用集合的源码分析:ArrayList

(周期计划-7)常用集合的源码分析:ArrayList

作者: 咸鱼正翻身 | 来源:发表于2018-04-28 17:57 被阅读25次

    写在前面

    最近因为拥抱变换,所以开始无奈的面试之路。因为在集合的源码分析上,出了些问题,所以这段时间,好好重新理一理常用的集合源码。(版本基于JDK1.7)

    感兴趣的看官,可以看看我的其他文章:
    1、常用集合的源码分析:HashMap
    2、Java反射实践:从反射中理解class
    3、从公司项目配置看Gradle


    ArrayList

    毫无疑问,提到常用集合。ArrayList势必是第一个被搬出来的,因此我们就先拿它开刀了。

    add(E e)

    1、初始化

    ArrayList的初始化,只有在第一次add的时候进行new数据,数组默认容量是10。

    private static final int DEFAULT_CAPACITY = 10;

    2、扩容

    每次add,第一步先初始化,初始化过后,开始判断是否需要扩容。
    扩容的判断,需要借助内部的一个size变量,这个变量用于统计当前数组的真实容量。也就是说我们的每一次add,size都会++。因此在我们进行扩容判断的时候,就是通过这个size和数组的当前最大容量进行比较。
    如果if (minCapacity - elementData.length > 0)其中minCapacity==size+1。满足这个条件说明需要扩容。
    扩容的过程比较直接,直接上代码:

    
    private void grow(int minCapacity) {
            int oldCapacity = elementData.length;
            //扩容1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //越界判断
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            //拷贝数组
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    
    
    

    3、赋值

    赋值的过程,就没什么好说的了:

    elementData[size++] = e;
    

    OK,add方法的过程就先看到此。非常简单的过程。1、第一次add,初始化一个容量10的数组。2、每次add()需要判断是否需要扩容。3、扩容过程完毕,把数据查到数组中。


    add(int index, E element)

    接下来,让我们看一个往明确的下标中add的方法。

    1、第一步

    刚开始的过程没什么好说的,就是比add(E e)的过程,多一步先判断index是否越界;然后依旧是初始化,和扩容的过程。

    //判断是否越界
    rangeCheckForAdd(index);
    //初始化以及扩容
    ensureCapacityInternal(size + 1); 
    
    

    2、移动数组

    既然我们是往某个下标下插值,那么插入之前,我们就要给要插入的index空出位置来,也就是数组整体移动位置。也就是下边这行代码。

    System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
    

    通过这个操作我们能看到一个问题,那就是如果我们连续进行相同的add(int index,E e)操作,那么很明显不会出现覆盖。所以,如果我们想要有覆盖的操作,需要调用set系列方法(后续会对此进行分析)。

    这里必须得提一点,这里的操作因为需要移动数组,因此对于ArrayList来说,这是一个效率比较低的操作。
    也印证了我们老生常谈的话题:数组适合随机读取;链表适合增加与删除。

    3、赋值

    没啥好说的,同上:

    elementData[size++] = e;
    

    get(int index)

    关于get操作,其实没啥好特别留意的,很普通的操作。因为直接返回对应数组的index下标即可呐。

    public E get(int index) {
            //判断是否越界
            rangeCheck(index);
            return elementData(index);
    }
    
    

    set(int index, E element)

    其实覆盖操作,也没什么难,就是单纯把index那个数据覆盖了,仅此而已。

        public E set(int index, E element) {
            //判断越界问题
            rangeCheck(index);
            //覆盖数据
            E oldValue = elementData(index);
            elementData[index] = element;
            return oldValue;
        }
    
    

    remove(int index)

    remove的操作也没有特别需要注意的地方。基本上贴上代码,就能很清楚的明白:

        public E remove(int index) {
            rangeCheck(index);
    
            modCount++;
            E oldValue = elementData(index);
    
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null;
    
            return oldValue;
        }
    
    

    基本上到这一步关于ArrayList的正常使用的分析就告一段落。当然,我们提到ArrayList,难免会扯到Vector身上。虽然它的身份略显尴尬,充其量就是一个跑龙套的角色。
    而且它内部的保证线程安全的操作,仅仅是使用了synchronized关键字而已。因为就不增加篇幅去梳理它了。


    尾声

    结束ArrayList的分析,我们可以发现,ArrayList的实现,相对比较的简单。需要留意的点比较的少,大概就剩下了如果此次add,超出了现有数组容量,进行1.5倍扩容。

    本菜开源的一个自己写的Demo,这个项目拆解并组合了很多业务。目的在于遇到类似业务,可以快速的ctrl+c/v。希望能给Androider们有所帮助,水平有限,见谅见谅…
    https://github.com/zhiaixinyang/PersonalCollect

    相关文章

      网友评论

        本文标题:(周期计划-7)常用集合的源码分析:ArrayList

        本文链接:https://www.haomeiwen.com/subject/qcvjlftx.html