ArrayList source code analysis! ! !

ArrayList inheritance structure analysis system

Here Insert Picture Description
Inheritance can be drawn from source architecture of FIG less ArrayList for analysis. From this figure we can see the structure of the abstract class AbstractList <E> implements List <E> interface, and its subclasses ArrayList <E> and achieve it again, doing it not bother you? ? Think about it why this design? ?


Here Insert Picture Description

This is designed for use in dynamic proxies, the proxy class must implement the interface directly, not indirectly by a parent, or an interface type that can not be downward transition in the method returns an object.

About the specific introduction of dynamic proxies, you can refer to: Design Patterns proxy mode - Static and dynamic agent Proxy


Interface is a specification of behavior, provides a number of set of methods common in the Collection interface, such as: add (), addAll () , remove (), removeAll (), size (), isEmpty (), containsAll () , etc.
Here Insert Picture Description
AbstractCollection <E> portion Code

public abstract Iterator<E> iterator();

public abstract int size();

public boolean add(E e) {
    throw new UnsupportedOperationException();
}
    
public boolean isEmpty() {
    return size() == 0;
}

public boolean contains(Object o) {
    Iterator<E> it = iterator();
    if (o==null) {
        while (it.hasNext())
            if (it.next()==null)
                return true;
    } else {
        while (it.hasNext())
            if (o.equals(it.next()))
                return true;
    }
    return false;
}

In the abstract class AbstractCollection <E>, the method implements most Collection <E> interface, as part of the method: iterator (), size () method is not implemented, but put them to the sub-class to implement; the Add () direct method throws an exception, need to subclass overrides; at the same time most of the use of these two methods is not achieved in the implementation of other methods, used here thinking what design patterns? ? ?

The method used here stencil pattern design mode, the abstract class AbstractCollection <E> are equivalent to those already implemented method of stencil, while those not implemented as: size (), iterator () is an abstract method to be subclassed achieved, the Add () method is equivalent to the hook method requires the child class overrides.

AbstractList<E> 和 AbstractCollection<E> 一样,都是使用了模板方法,具体就不在这里赘述了。

下面正式分析 ArrayList<E> 源码

ArrayList<E> 源码分析

构造器和 add(E e) 方法

ArrayList 有 3 个构造器,一般以下两个用的比较多
Here Insert Picture Description
Here Insert Picture Description
ArrayList 底层使用 Object[ ] 数组存储数据,当使用无参构造器时,默认是空数组。

当第一次调用 add() 方法时,size 是成员变量,记录数组中元素个数,初始为 0;这里将 size+1 然后调用 ensureCapacityInternal() 方法。并将 1 传进去。
Here Insert Picture Description
calculateCapacity() 方法作用:当容器数组为默认的空数组时,返回默认容量大小与 minCapacity 的最大值,否则直接返回 minCapacity。此时返回默认容量大小 10
Here Insert Picture Description
然后调用 ensureExplicitCapacity() 方法,并将 10 作为参数传进去,此时当前数组长度为 0,所以调用 grow() 方法,并将 10 传进去
Here Insert Picture Description
grow() 方法是数组扩容的核心方法!!!
Here Insert Picture Description
Here Insert Picture Description
从 grow() 方法中我们可以得知:

  1. newCapacity = oldCapacity + oldCapacity / 2,数组增长因子为 0.5

  2. 当 minCapacity 值在 oldCapacity 与 newCapacity 之间时,数组的最终扩容大小为 newCapacity;

  3. 当 minCapacity 值在 newCapacity 与 MAX_ARRAY_SIZE 之间时,数组的最终扩容大小为 minCapacity;

  4. 当 minCapacity 值大于 MAX_ARRAY_SIZE 时,数组最终大小为 Integer.MAX_VALUE
    Here Insert Picture Description
    因此,当使用无参构造器创建 ArrayList 对象时,数组容量默认大小为 10


总结:

  1. 在创建 ArrayList 对象时,最好使用带参构造器指定初始化容量,避免在 add() 操作时消耗 CPU,因为在数组进行复制时消耗是非常巨大的

  2. 数组默认大小容量为 10

  3. 数组的增长因子为 0.5

remove(int index) 方法

Here Insert Picture Description
Here Insert Picture Description
在 remove() 方法中,我们主要关注两个方法,一是 rangeCheck() 检查下标越界问题,这没啥好说的,还有一个是 System.arraycopy() 调用的是本地方法。

当移除的是数组中最后一个元素是,直接将最后一个元素设为 null,否则进行数组拷贝,将数组中的元素往前挪一位

add(int index, E element)

Here Insert Picture Description
和前面类似,这里就不重复赘述了!!!

批量删除 batchRemove(Collection<?> c, boolean complement)

Here Insert Picture Description
不管是 retainAll 还是 removeAll 都会调用 batchRemove 方法
Here Insert Picture Description
Here Insert Picture Description
以 removeAll() 为例:removeAll 中参数 complement 为 false

removeAll() 求差集,在该方法中 r 代表读指针,w 代表写指针。r 和 w 初始都为 0,w 总是小于等于 r

在第一个 for 循环中,r 遍历数组中所有元素个数(注意:元素个数 size ≤ 数组长度 length)

if (c.contains(elementData[r]) == complement)
	elementData[w++] = elementData[r];

每当 c.contains(elementData[r]) == false 时,if 为 true,便将读指针 r 指向的元素赋值给写指针 w 指向的位置。

当读指针 r 循环到 size 时便跳出循环,此时写指针 w 指向的位置之后的数组中的元素最终在 finally 语句块中都将被赋值为 null,modCount 值也会被修改。

if (w != size) {
	// clear to let GC do its work
    for (int i = w; i < size; i++)
        elementData[i] = null;
    modCount += size - w;
    size = w;
    modified = true;
}

从源码中我们可以发现批量删除不会改变数组大小,但 modCount 值仍会被修改。

modCount

Here Insert Picture Description
modCount 是 AbstractList<E> 中的成员变量,记录这个列表在结构上被修改的次数。

从源码中可以发现每当进行 add 或 remove 操作时 modCount 值都会随着改变

关于在 foreach 中增加或删除元素

Here Insert Picture Description
Here Insert Picture Description
从调试中我们可以发现 foreach 的本质是使用了 iterator 迭代器
Here Insert Picture Description
在 ArrayList 内部有一个内部类实现了 Iterator<E> 接口,每当使用 foreach 时都会返回该迭代器对象
Here Insert Picture Description
当返回该对象时,游标 cursor 初始值为 0,expectedModCount = modCount,当调用 hasNext() 方法时,判断 cursor 值是否与数组中元素个数相等,不等则返回 true
Here Insert Picture Description
调用 next() 方法时,首先检查数组元素个数是否被修改过
Here Insert Picture Description
因此当 foreach 中内部第一次增加或删除元素时,modCount 会改变,而 expectedModCount 的值没变,当下一次进入 foreach 时,就会抛出并发修改异常。
Here Insert Picture Description

get 和 set 方法非常简单

Here Insert Picture Description

三、总结

  1. ArrayList 底层使用数组实现,默认初始容量为 10,增长因子为 0.5,最好自己指定初始化容量。

  2. 适合随机查找和遍历,不适合插入和删除。因为当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。

  3. 当数组大小不满足需要增加存储能力时,可以自动扩容。

Published 42 original articles · won praise 11 · views 3817

Guess you like

Origin blog.csdn.net/weixin_44584387/article/details/104508757