Java源码系列之ArrayList

Posted wanmeilingdu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java源码系列之ArrayList相关的知识,希望对你有一定的参考价值。

Java源码系列之ArrayList

一、介绍

  • ArrayList是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。它继承了AbstractList,实现了List,RandomAccess,Cloneable,java.io.Serializable这些接口。
  • ArrayList继承了AbstractList,实现了List.它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
  • ArrayList实现了RandomAccess接口,即提供了随机访问功能,RandomAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。稍后,我们会比较List的快速随机访问和通过Iterator迭代器访问的效率。
  • ArrayList实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
  • ArrayList实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
  • 和Vector不同,ArrayList中的操作不是线程安全的,所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或则CopyOnWriteArrayList.

它的继承类图关系如下:

二、源码分析

基本属性

/**
     * 默认初始容量,大小为10.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空的数组,传入容量为0时使用.
     */
    private static final Object[] EMPTY_ELEMENTDATA = ;

    /**
     * 空的数组,传入容量时使用,当容器添加第一个元素时,会进行扩容,具体见add(E e)方法.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = ;

    /**
     * 真正存储元素的数组
     */
    transient Object[] elementData; // non-private to simplify nested class access
	
	/**
	* 要分配的数组的最大值
	*/
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  • DEFAULT_CAPACITY :默认容量为10,也就是通过new ArrayList()创建时的默认容量。

  • EMPTY_ELEMENTDATA :空的数组,这种是通过new ArrayList(0)创建时用的是这个空数组。

  • DEFAULTCAPACITY_EMPTY_ELEMENTDATA:也是空数组,这种是通过new ArrayList()创建时用的是这个空数组,与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(10)个元素。

  • elementData:真正存放元素的地方,使用transient是为了不序列化这个字段。

  • size:真正存储元素的个数,而不是elementData数组的长度。

  • MAX_ARRAY_SIZE:数组长度的最大值是Integer.MAX_VALUE - 8

    数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节。

    如果数组长度过大,可能出现的两种错误

    OutOfMemoryError: Java heap space 堆区内存不足(这个可以通过设置JVM参数 -Xmx 来指定)。

    OutOfMemoryError: Requested array size exceeds VM limit 超过了JVM虚拟机的最大限制,我的window64就是 Integer.MAX_VALUE-1 .

构造方法之ArrayList(int initialCapacity)

	public ArrayList(int initialCapacity) 
        if (initialCapacity > 0) 
            //如果传入的初始容量值大于0,则初始化一个初始容量大小的数组,用来存储元素
            this.elementData = new Object[initialCapacity];
         else if (initialCapacity == 0) 
            //如果等于0,则把空数组EMPTY_ELEMENTDATA赋给elementData
            this.elementData = EMPTY_ELEMENTDATA;
         else 
            //如果小于0,则抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        
    

构造方法之ArrayList()

	public ArrayList() 
        //使用空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,在添加第一个元素的时候会扩容
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    

构造方法之ArrayList(Collection<? extends E> c)

	public ArrayList(Collection<? extends E> c) 
        //传入的集合转为数组,并赋值给elementData
        elementData = c.toArray();
        //判断传入的集合的大小是否为0
        if ((size = elementData.length) != 0) 
            //判断集合toArray()返回的结果是否是Object[].class,如果不是,则重新拷贝成Object[].class类型
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
         else 
            //如果传入的集合为0,则初始化为空数组EMPTY_ELEMENTDATA
            this.elementData = EMPTY_ELEMENTDATA;
        
	

方法之add(E e)

	/**
	* 添加元素
	*/
	public boolean add(E e) 
        //检查是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //添加元素到最后一位
        elementData[size++] = e;
        return true;
    

	private void ensureCapacityInternal(int minCapacity) 
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) 
            //如果elementData为空数组,则取默认容量和传入的所需最小容量(minCapacity)之中大的值
            //并把该值重新赋值给minCapacity
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        

        ensureExplicitCapacity(minCapacity);
    

    private void ensureExplicitCapacity(int minCapacity) 
        // 修改次数加1,说明这是一个支持fail-fast的集合
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            //真正的扩容方法
            grow(minCapacity);
    

	private void grow(int minCapacity) 
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //新的容量是老容量1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            //如果新容量比所需最小容量小,则把所需最小容量值赋给新的容量
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //如果新容量要大于所允许的最大长度,则会使用最大容量为Integer的最大值,
            //否则使用MAX_ARRAY_SIZE
            newCapacity = hugeCapacity(minCapacity);
        //以新容量拷贝出来一个新数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    

    private static int hugeCapacity(int minCapacity) 
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    

方法之add(int index, E element)

	/**
	* 添加元素到指定位置
	*/
	public void add(int index, E element) 
        //检查是否越界
        rangeCheckForAdd(index);
        //检查是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将下标为index及之后的元素向后移一位,这样index就空出来了
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //将元素插入到index的位置
        elementData[index] = element;
        //大小加1
        size++;
    

	private void rangeCheckForAdd(int index) 
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    

方法之addAll(Collection<? extends E> c)

	/**
	* 求两个集合的并集
	*/
	public boolean addAll(Collection<? extends E> c) 
        //将集合c转为数组
        Object[] a = c.toArray();
        //集合a的大小
        int numNew = a.length;
        //扩容所需的容量=已有数组的大小+c集合的大小
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //将集合c的元素,拷贝到数组的最后
        System.arraycopy(a, 0, elementData, size, numNew);
        //size加上c集合的大小
        size += numNew;
        //如果集合c不为空,返回true,否则返回false
        return numNew != 0;
    

方法之get(int index)

	/**
	* 获取指定位置的元素
	*/
	public E get(int index) 
        //检查是否越界
        rangeCheck(index);
        //返回数组index的元素
        return elementData(index);
    

	private void rangeCheck(int index) 
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    
	
	E elementData(int index) 
        return (E) elementData[index];
    

注:获取指定索引位置的元素,时间复杂度为O(1)。

方法之remove(int index)

    /**
    * 删除指定位置元素
    */
	public E remove(int index) 
        //检查是否越界
        rangeCheck(index);

        modCount++;
        //获取index位置的元素
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        //如果index不是最后一位,则将index之后的元素往前挪一位
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,numMoved);
        //讲最后一个元素删除,置空,帮助GC回收
        elementData[--size] = null; // clear to let GC do its work
        //返回旧值,即删除的index对应的值
        return oldValue;
    

注:删除指定索引位置的元素,时间复杂度为O(n);而且从源码上可以看出,删除元素后ArrayList并没有缩容。

方法之remove(Object o)

	/**
	* 删除指定元素值的元素
	*/
	public boolean remove(Object o) 
        if (o == null) 
            //遍历整个数组,找到元素第一次出现的位置,并将其快速删除
            for (int index = 0; index < size; index++)
                //如果要删除的元素为null,则使用==进行比较,满足条件则进行快速删除,并返回true
                if (elementData[index] == null) 
                    fastRemove(index);
                    return true;
                
         else 
            //遍历整个数组,找到元素第一次出现的位置,并将其快速删除
            for (int index = 0; index < size; index++)
                //如果要删除的元素不为null,则使用equals进行比较,满足条件则进行快速删除,并返回true
                if (o.equals(elementData[index])) 
                    fastRemove(index);
                    return true;
                
        
        return false;
    

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) 
        //和remove(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
    

方法之retainAll(Collection<?> c)

	/**
	* 求两个集合的交集
	*/
	public boolean retainAll(Collection<?> c) 
        //校验集合c是否为null,为null则抛出NullPointerException
        Objects.requireNonNull(c);
        //调用批量删除方法,这时complement传入true,表示删除不包含在c中的元素
        return batchRemove(c, true);
    

    /**
     * 批量删除
     * @param c 集合
     * @param complement 为true,表示删除不包含在c中的元素,否则表示删除包含c中的元素
     * @return
     */
    private boolean batchRemove(Collection<?> c, boolean complement) 
        final Object[] elementData = this.elementData;
        //使用读写两个指针同时遍历数组
        //读指针每次自增1,写指针放入元素的时候才加1
        //这样不需要额外的空间,只需要在原有的数组上操作就可以了
        int r = 0, w = 0;
        boolean modified = false;
        try 
            //遍历整个数组,如果c中包含该元素,则把该元素放到写指针的位置(以complement为准)
            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.
            //正常来说r最后是等于size的,除非c.contains()抛出了异常
            if (r != size) 
                //如果c.contains()抛出了异常,则把未读的元素都拷贝到写指针之后
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            
            if (w != size) 
                //将写指针之后的元素置为空,帮助GC回收
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                //新的大小等于写指针的位置,因为每写一次写指针就加1,所以新大小正好等于写指针的位置
                size = w;
                //有修改,设置modified=true
                modified = true;
            
        
        return modified;
    

方法之removeAll(Collection<?> c)

	/**
	* 删除包含c集合的元素,即求差集
	*/
	public boolean removeAll(Collection<?> c) 
        //检查集合c不能为null
        Objects.requireNonNull(c);
        //批量删除,complement为false,表示删除包含在c中的元素
        return batchRemove(c, false);
    

elementData之序列化

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException
    //防止序列化期间有修改
    int expectedModCount = modCount;
    //写出非transient非static属性(会写出size属性)
    s.defaultWriteObject();

    //写出元素个数
    s.writeInt(size);

    //依次写出元素
    for (int i=0; i<size; i++) 
        s.writeObject(elementData[i]);
    

    //如果有修改,抛出异常
    if (modCount != expectedModCount) 
        throw new ConcurrentModificationException();
    


private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException 
    //声明为空数组
    elementData = EMPTY_ELEMENTDATA;

    //读入非transient非static属性(会读取size属性)
    s.defaultReadObject();

    //读入元素个数,没什么用,只是因为写出的时候写了size属性,读的时候也要按顺序来读
    s.readInt();

    if (size > 0) 
        //计算容量
        int capacity = calculateCapacity(elementData, size);
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
        //检查是否需要扩容
        ensureCapacityInternal(size);

        Object[] a = elementData;
        //依次读取元素到数组中
        for (int i=0; i<size; i++) 
            a[i] = s.readObject();
        
    

注:

查看writeObject()方法可知,先调用s.defaultWriteObject()方法,再把size写入到流中,再把元素一个一个的写入到流中。

一般地,只要实现了Serializable接口即可自动序列化,writeObject()和readObject()是为了自己控制序列化的方式,这两个方法必须声明为private,在java.io.ObjectStreamClass#getPrivateMethod()方法中通过反射获取到writeObject()这个方法。

在ArrayList的writeObject()方法中先调用了s.defaultWriteObject()方法,这个方法是写入非static非transient的属性,在ArrayList中也就是size属性。同样地,在readObject()方法中先调用了s.defaultReadObject()方法解析出了size属性。

elementData定义为transient的优势,自己根据size序列化真实的元素,而不是根据数组的长度序列化元素,减少了空间占用。

总结

  • ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,ArrayList不会进行缩容;
  • ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);
  • ArrayList添加元素到尾部极快,平均时间复杂度为O(1);
  • ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n);
  • ArrayList从尾部删除元素极快,时间复杂度为O(1);
  • ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为O(n);
  • ArrayList支持求并集,调用addAll(Collection<? extends E> c)方法即可;
  • ArrayList支持求交集,调用retainAll(Collection<? extends E> c)方法即可;
  • ArrayList支持求单向差集,调用removeAll(Collection<? extends E> c)方法即可;

以上是关于Java源码系列之ArrayList的主要内容,如果未能解决你的问题,请参考以下文章

java集合系列之ArrayList源码分析

Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例

Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例

Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例

Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)

Java入门系列之集合LinkedList入门