ArrayList源码 基本功能分析

人走茶凉 提交于 2020-01-13 01:23:26

继承与实现

函数名 介绍
AbstractList
List接口 继承自Collection,祖父Iterable
RandomAccess 标志接口,表明实现的这个接口的List集合是支持快速随机访问的。
Cloneable 接口 覆盖clone函数,能被克隆。
java.io.Serializable 接口 支持序列化,能够序列化传输

空数组

  首先,我观察到了。为毛定义了两个空数组呢~( ̄▽ ̄)。

    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

  既然是list有空的情况和非空的情况,定义一个来表示空的情况不就行了?
  为何
构造函数中默认大小和定义大小的两种情况要设置两个静态的空数组。
  我怀着无知的大脑查看了一下两个空数组的应用场景。

EMPTY_ELEMENTDATA

自定义容量的构造函数

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //创建initialCapacity大小的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //创建空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

指定集合的构造函数

    public ArrayList(Collection<? extends E> c) {
        //
        elementData = c.toArray();
        //如果指定集合元素个数不为0
        if ((size = elementData.length) != 0) {
            // c.toArray 可能返回的不是Object类型的数组所以加上下面的语句用于判断,
            //这里用到了反射里面的getClass()方法
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 用空数组代替
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

  若是自己放个空集合/设置容量0,就用得着这 EMPTY_ELEMENTDATA

DEFAULTCAPACITY_EMPTY_ELEMENTDATA

  默认(什么都没设置)情况下:

 public ArrayList() {
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
 }
//扩容的时候,看看是不是默认设置的空数组。
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
//得到最小扩容时
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) 

  也就是说,扩容数组的时候,判断的是数组是不是和DEFAULTCAPACITY_EMPTY_ELEMENTDATA相等,而并不是前者。
  这意思就是如果是默认空数组的话,就先扩容个DEFAULT_CAPACITY(一般是10)再说。
  哦哦,我似乎有点明白了,ArrayList中默认数组和指定容量数组的处理是不同的,默认数组第一次分配就会给足10的空间,而指定容量的list就没这个待遇。
  我猜想这是根据某种规律设计的,具体如何便是不得而知啦。

异常

  • IllegalArgumentException: 这里是针对用户给出的奇怪的自定义容量。
  • OutOfMemoryError:list虽好,可不能贪啊。
  • InternalError
  • IndexOutOfBoundsException

常规函数实现

contains

  确认是否包含,内部调用indexOf方法。

indexOf

  for循环查找数组,找到第一个就返回。一个注意点就是这玩意还找null值。找不着就反个-1。

lastIndexOf

&emsp;&emsp;和上面那位一个尿性,从后向前遍历罢了。

clone

            ArrayList<?> v = (ArrayList<?>) super.clone();
            //Arrays.copyOf功能是实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;

  首先,super.clone做了什么,我查了一下,如果不重写clone()方法,则在调用clone()方法实现的是浅复制(所有的引用对象保持不变)。
  list中属性也就下面这几位。

    private static final long serialVersionUID;
    private static final int DEFAULT_CAPACITY = 10;
	private static final Object[] EMPTY_ELEMENTDATA = {};
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	transient Object[] elementData;
    private int size;

  其中引用也就EMPTY_ELEMENTDATA ,DEFAULTCAPACITY_EMPTY_ELEMENTDATA和elementData。
  所以说,clone中浅拷贝基本没起什么作用,消耗最多的还是调用copyOf。

toArray

  

    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // 新建一个运行时类型的数组,但是ArrayList数组的内容
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
            //调用System提供的arraycopy()方法实现数组之间的复制
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

  first at all,java.lang.SuppressWarnings是J2SE5.0中标准的Annotation之一,告诉编译器忽略指定的警告。
  看官,且看这T[] a参数,来者意欲何为?
我看,最重要的是这个a.getClass()。
  咱要的是返回一个特定类型的数组,list的内置数组类型是Object,咱只能返个T[]类型的数组出去。不然咋整?故而把a当作一个“模板”传了进来。多数情况下都是传的new T[0]进来。
  咱们来看看源代码。

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

  咱们可以看到,在模板中创建数组是要用Array.newInstance函数的。

add(int index, E element)

  首先调用rangeCheckForAdd(index)来检测输入的参数是否合法。
  然后调用ensureCapacityInternal(size + 1);,保证空间足够。
  System.arraycopy(elementData, index, elementData, index + 1,size - index);
将index后头的元素全部后移动一格。这个操作还是蛮耗时的,建议别老是没事用它。特别是别变态到老用add(0,element)。

remove(Object o)

  注意,是从列表中删除第一个出现的指定元素。

batchRemove(Collection<?> c, boolean complement)

  咱们先来看看两个调用batchremove的函数,这俩家伙先加插了传入的参数是不是空引用,然后调用该函数。值得注意的是,一个设置true,一个设置false。

    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        //如果此列表被修改则返回true
        return batchRemove(c, false);
    }

    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }

那么,我们就知道,complement的设置表示下面两种意义:

  • true(保留):保留和集合内元素相同的list元素。
  • false(删除):删除和集合内元素相同的list元素。

  这个函数是一个try、catch结构。看官,请看下面finally里面这段儿。这finally就是发生异常也会执行的一段代码。所以它确保了发生错误,原来数组里面存储的内容也不会就此丢失。
   咱们还得注意一下,在java中常常用elementData[i] = null等待来设置引用为null,这样垃圾回收器就能来处理。
  最后反个boolean变量,标明有没有修改。

	try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            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;
            }
        }

内部类

函数 继承 实现
Itr Iterator
ListItr Itr ListIterator
SubList AbstractList RandomAccess
ArrayListSpliterator Spliterator

注意事项

  或许关于数组,我们还需要注意到这一点。

length 数组
length() 字符串
size() 泛型集合

总结

  ArrayList总结一句话,一个copyOf打天下!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!