JDK源码阅读之ArrayList

ArrayList简介

List 接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是不同步的。)

从这句话中我们可以看出ArrayList的两个特点,1-大小可变,2-基于数组实现

size、isEmpty、get、set、iterator 和 listIterator 操作都以固定时间运行。add 操作以分摊的固定时间 运行,也就是说,添加 n 个元素需要 O(n) 时间。其他所有操作都以线性时间运行(大体上讲)。与用于 LinkedList 实现的常数因子相比,此实现的常数因子较低。

从这段话中我们读出的意思是:add方法与其他方法不一样,其实他也是这个类的核心方法。

每个 ArrayList 实例都有一个容量。该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。并未指定增长策略的细节,因为这不只是添加元素会带来分摊固定时间开销那样简单。

从这里我们看出:ArrayList中有一个重要的变量:容量,他会贯穿这个类的核心
另外注意一点是:ArrayList不是线程安全的,而ArrayList替代的集合类Vector就是一个线程安全的类

ArrayList类图

ArrayList类图
ArrayList 实现了List接口,RandomAccess接口–代表着可以随机访问,实现了Cloneable接口,可以实现复制,这里的复制不是浅复制是深拷贝。还实现了Serializable接口,实现序列化。继承了AbstractList接口。

ArrayList的重要方法简介

构造方法

public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } 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();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

构造方法一共有三个,其中一个是默认的构造函数,这个函数会初始化一个空数组,添加元素后会扩容到默认的容量大小10!第二个构造函数是指定容量,还是初始化一个空的数组,但是添加元素的时候不会扩容到默认的容量大小。第三个构造函数的参数是一个集合,他会将集合中的元素以及大小记录。(注意:入宫容量为0,或者集合为空,那么处理方法是和第一个构造函数一致的)

精华方法

public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
    
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

扩容的步骤可以概括为:

  1. 检验容量是否足够;minCapacity - elementData.length > 0,一旦需要的容量大于现有的容量,那么立即扩容。
  2. 扩充的容量是当前容量的1.5倍
  3. 检验容量是否足够满足需要的容量
    补充:这里的扩量的1.5倍没有使用乘法,而是使用左移运算 oldCapacity + (oldCapacity >> 1)。

重点方法

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; // clear to let GC do its work

        return oldValue;
    }
public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

从源码中我们看出arrayList的移除操作还是比较复杂的,首先是确定需要移除的元素的下标,然后将后面的元素一一的往前移动,将最后一个元素置为null,并且将大小改变。其实从底层的实现我们可以猜到删除元素这一个过程是怎样的。
另外他们都有一个共同点就是对modCount这个变量加1了。

clean

public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

一个循环将所有的元素置为null

clone

public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

实现的是深拷贝,不是浅复制

一些简单的,不常用的就不复制粘贴了,阅读源码的时候会有不一样的感受。

补充

ArrayList也自己实现了两个迭代器和一个包装外壳SubList。

ArrayList的阅读感想

ArrayList和父类相比,最明显的特点是:父类是不可改变的集合,而ArrayList是大小可变的集合。和Vector类相比,最明显的特点是:Vector是线程安全的,ArrayList不是线程安全。所以ArrayList的核心就是怎么实现大小可变,也就是扩容的方法。

说明

本文是本人撰写,如果对于读者有什么启发或者帮助,那是我的荣幸。如果对于本文中有什么错误,或者意见,可以留言或者联系我:zlh8013gsf@126.com

©️2020 CSDN 皮肤主题: 创作都市 设计师:CSDN官方博客 返回首页