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

写在前面

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

上一篇:zabbix基础详解及安装配置


下一篇:Java反射实践:从反射中理解class