Java集合系列之ArrayList

1,040 阅读4分钟

Java集合系列之ArrayList

Hello,大家好,上一篇Java集合系列给大家讲了HashMap,其实工作中,如果不设计到并发编程,HashMap和ArrayList用的是最多的,这一篇就给大家分享一下ArrayList,这个集合底层贼简单。其实任何使用ArrayList的地方都可以使用数组代替,ArrayList可以理解为动态数组,它的容量可以自动扩大,同时也暴露了API出来手动扩大。大家想一想,你用数组的时候其实不是一样吗?有时手动的调用Arrays.copyOf或者System.arraycopy(),其实ArrayList底层也是这么用的。呵呵,是不是so easy .好了,老套路,文章结构:

  1. ArrayList 大体介绍
  2. ArrayList API
  3. ArrayList扩容

1. ArrayList 大体介绍

ArrayList确实是太简答了,也没个什么好介绍的,哈哈,底层就是一个数组。然后实现 List 接口,它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

2. ArrayList API

说下API吧,直接上代码,大家看下代码里面注释了,太简答了,需要注意的地方我会在源码后面提一下:

构造方法

//默认数组长度为10
public ArrayList() {
        this(10);
    }
//可以指定数组初始长度
public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
//可以传进来一个Collection,然后内部转换成数组。
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

存储数据

set(int index, E element):该方法首先调用rangeCheck(index)来校验 index 变量是否超出数组范围,超出则抛出异常。而后,取出原 index 位置的值,并且将新的 element 放入 Index 位置,返回 oldValue。

add(E e):该方法是将指定的元素添加到列表的尾部。当容量不足时,会调用 grow 方法增长容量。

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(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:
        //注意这里扩容时使用的Arrays.copyOf
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

add(int index, E element):在 index 位置插入 element,注意这个不是覆盖,会把后面的元素都向后靠一个位置。

addAll(Collection<? extends E> c) 和 addAll(int index, Collection<? extends E> c):将特定 Collection 中的元素添加到 Arraylist 末尾。

读取

get(int index):略

3. ArrayList扩容

从上面介绍的向 ArrayList 中存储元素的代码中,我们看到,每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。数组扩容有两个方法,其中开发者可以通过一个 public 的方法ensureCapacity(int minCapacity)来增加 ArrayList 的容量,而在存储元素等操作过程中,如果遇到容量不足,会调用priavte方法private void ensureCapacityInternal(int minCapacity)实现。

public void ensureCapacity(int minCapacity) {
        if (minCapacity > 0)
            ensureCapacityInternal(minCapacity);
    }

    private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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.5 倍(从int newCapacity = oldCapacity + (oldCapacity >> 1)这行代码得出)。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造 ArrayList 实例时,就指定其容量,以避免数组扩容的发生。

其次,这里给大家提一下这个扩容时使用到的Arrays.copyOf方法:

 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

说白了,干了什么事呢,根据newLength创建了一个数组,然后调用System.arraycopy把数据拷贝了过去。这个System.arraycopy又是个native方法,据说速度很快,关于深拷贝还是浅拷贝,我这里不给大家做试验了,直接做结论:

  • Arrays.copyOf 方法内部使用System.arraycopy
  • System.arraycopy是native方法。
  • System.arraycopy 重新创建了数组,在这一层面是深拷贝。但是数组内的元素是浅拷贝。说白了,如果old数组里面有一个Student对象,拷贝到新数组里其实是一个指向这个对象的指针,只是把指针拷贝了过去。

结语

好了好了,这个ArrayList我实在不知道怎么写,感觉没什么东西可以写,内部就是一个数组,然后新增的时候回自动调用Arrays.copyOf扩容。也可以手动扩容。 Over,Have a good day !