简单聊聊 ArrayList

哈喽,大家好,今天来聊一聊我们在Android开发中经常用的到一个类,ArrayList

ArrayList是以数组为底层实现的,并且可以动态的加减容量。但是要注意它不是线程安全的。


在分析源码之前,我们先来了解两个相关的知识点,这样更加有助于我们理解源码。

Arrays.copyof

这个方法是用于复制数组的方法

    public static  T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

我们可以看到这里是传入了两个参数,第一个参数是要复制的数组,第二个参数是复制后数组的长度,如果传入长度和original的长度一样,那么就会复制original里面所有的数据。如果比original的长度短,那么久复制到相应长度即可,如果比original的长度还要长,那么就会用null或者0来填充。

System arraycopy

我们继续跟进上面Arrays.copyof的源码我们会发现:

    public static  T[] copyOf(U[] original, int newLength, Class newType) {
        @SuppressWarnings("unchecked")
        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;
    }

    @FastNative
    public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos, int length);

我们发现Arrays.copyof最终调用的就是System.arraycopy方法,这是一个native方法,参数解释如下:

  • src:要复制的数组。
  • srcPos:复制的起点。
  • dest:复制的目标数组。
  • destPos:目标数组复制的起点
  • length:要复制的值的个数
    要注意的是这里复制过后修改复制后的新数组并不会影响原来的数组的值。

接下来我们进入正题,开始聊聊ArrayList。

我们先来看看ArrayList实现了哪些接口

public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable
  • ArrayList实现了List这个接口,在List里我们可以看到有add,get,set等方法。
  • RandomAccess是一个标志接口,实现RandomAccess这个接口后ArrayList支持快速随机访问。
  • 实现Serializable后ArrayList支持序列化。

接下来我们来看看类里面的方法:

ArrayList(int initialCapacity)

    private static final Object[] EMPTY_ELEMENTDATA = {};

    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);
        }
    }

这个构造函数传入的initialCapacity为初始化ArrayList的长度。如果长度大于0,则new一个长度为initialCapacity的数组并赋值给elementData。如果长度为0,这直接将默认空数组赋值给elementData。如果长度小于0,则报错。

ArrayList()

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

这个无参构造函数直接是将一个空数组赋值给elementData。

ArrayList(Collection c)

    public ArrayList(Collection c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

这个构造函数传入了一个集合,先将传入的参数转换为数组并赋值给elementData。再进行elementData数组的长度判断,如果长度大于0,将elementData数组转换为一个Object的数组。如果elementData长度为0,则直接赋值一个空数组。

indexOf(Object o)

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

这里先判断传入参数是否为null,因为ArrayList是可以添加null的,所以我们要先判断一下,利用for循环来一个一个对比,如果为null,则返回下标。如果不为null,也是在for循环中做值的对比,注意这里用的equals来对比的值而不是用的==,找到后返回下标,如果没有则返回-1。

get(int index)

    public E get(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        return (E) elementData[index];
    }

get方法是获取对应下标的值,这里先判断传入的参数是否越界,如果没有直接从数组中取出对应的值,并返回。

set(int index, E element)

    public E set(int index, E element) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        E oldValue = (E) elementData[index];
        elementData[index] = element;
        return oldValue;
    }

set方法是修改对应下标的值,这里先判断传入的index是否越界,如果没有我们获取现在下标对应的值,然后赋上新的值,并将原来的值返回。

add(E e)

    public boolean add(E e) {
        //这里先判断是否需要增加数组的容量
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //赋值到最后一位。
        elementData[size++] = e;
        return true;
    }

    private static final int DEFAULT_CAPACITY = 10;

    private void ensureCapacityInternal(int minCapacity) {
        //如果数组在初始化的时候没有传入数组大小,我们先设置一个最小值。
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        //操作次数加1
        modCount++;

        //如果数组容量不够,需要增容。
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    private void grow(int minCapacity) {
        //获取现在数组的大小。
        int oldCapacity = elementData.length;
        //获取现在大小1.5倍的值。
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果还是不够大,那么就直接让数组大小为minCapacity。
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //如果超过了规定的最大值,将用传入的最小值来重新计算数组的大小。
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //最后赋值所有值到新的数组。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //如果符合条件的最小值大于规定的最大值,那么将值修改为Integer.MAX_VALUE,否则为规定的最大值。
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

关键解释已经注释在代码中。

add(int index, E element)

    public void add(int index, E element) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

这个add方法和上一个add方法大致相同,在ensureCapacityInternal(size + 1)方法后调用System.arraycopy方法将数组从index下标位置全部向后移动一位,然后直接将index下标位置赋上新传进来的值。

remove(int index)

    public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        modCount++;
        E oldValue = (E) 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;
    }

在remove(int index)方法中我们先判断传入的index是否越界,如果没有我们将index+1位置开始的所有值向前移动一位,这样就覆盖了index的值,然后将最后多出来的一位赋值为null,方便GC处理。

remove(Object o)

    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
    }

remove(Object o)方法我们需要先判断传入的Object是否为null,然后找到Object值的下标,因为这里已经找到了下标,所以肯定没有越界,然后直接调用fastRemove方法来删除。fastRemove方法和上面的remove方法类似,这里也不再解释了。

addAll(Collection c)

    public boolean addAll(Collection c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

这里先通过ensureCapacityInternal(size + numNew)方法来判断是否需要增容,然后直接调用System.arraycopy方法将新的数组赋值到elementData中。

addAll(int index, Collection c)

    public boolean addAll(int index, Collection c) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

addAll(int index, Collection c)方法先判断了传入的index是否越界,如果没有越界我们继续判断数组是否需要增容。然后这里有一个判断,如果numMoved 的值大于0,就说明index在数组的内,如果小于等于0,说明index在数组最后一位或者在数组外,根据不同,分别调用System.arraycopy来进行赋值。

removeAll(Collection c)

    public boolean removeAll(Collection c) {
        //先判断c是否为null。
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }

    private boolean batchRemove(Collection c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            //这里传入的complement为false,那么for中就是将不删除的是有值全部放到最前面去。
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            //如果r不等于size,那么说明上面的for循环报错了,这里将从报错开始的所有值赋值到报错前调整过位置
            //的不删除的值的后面。(这里有点没说清楚,大家看代码应该能懂 :))
            if (r != size) {
                System.arraycopy(elementData, r, elementData, w, size - r);
                w += size - r;
            }
            //w不等于size证明removeAll传入的c不为null。这里从w开始,将后面的所有值(上面for循环将不删除
            //的值全部放到了w前面)赋值为null。
            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;
            }
        }
        return modified;
    }

removeAll(Collection c)方法的主要解释都放在了代码里面,这个有点绕,大家多看看代码理解下应该没什么问题。

ArrayList的主要方法这里就分析完成了,我们可以发现ArrayList在删除,添加的时候会比较麻烦,在查询的时候比较简单。所以以后再多查询,少添加,少删除的时候可以考虑ArrayList。

上文中如果有错误的地方,欢迎大家指出,也欢迎大家留言交流。

3Q

你可能感兴趣的:(简单聊聊 ArrayList)