简单聊聊 ArrayList

2019-07-30  本文已影响0人  Jevely

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

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


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

Arrays.copyof

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

    public static <T> 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,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> 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方法,参数解释如下:


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

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

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

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

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<? extends E> c)

    public ArrayList(Collection<? extends E> 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<? extends E> c)

    public boolean addAll(Collection<? extends E> 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<? extends E> c)

    public boolean addAll(int index, Collection<? extends E> 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<? extends E> 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

上一篇下一篇

猜你喜欢

热点阅读