ArrayList源码分析(JDK1.8)

ArrayList简介 

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

属性

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
	// 序列化id
	private static final long serialVersionUID = 8683452581122892189L;
	// 默认初始的容量
	private static final int DEFAULT_CAPACITY = 10;
	// 一个空对象
	private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
	// 一个空对象,如果使用默认构造函数创建,则默认对象内容默认是该值
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
	// 当前数据对象存放地方,当前对象不参与序列化
	transient Object[] elementData;
	// 当前数组长度
	private int size;
	// 数组最大长度
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}

 构造器

如果使用的是无参构造器,将使用默认的对象数组赋给elementData。此时数组的size为0。

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

 如果传入初始容量,则代表指定ArrayList的初始数组长度,传入参数如果是大于0,则使用用户的参数初始化,如果用户传入的参数小于0,则抛出异常。

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

使用带Collection对象的构造函数
1)将collection对象转换成数组,然后将数组的地址的赋给elementData。
2)更新size的值,同时判断size的大小,如果是size等于0,直接将空对象EMPTY_ELEMENTDATA的地址赋给elementData
3)如果size的值大于0,则执行Arrays.copy方法,把collection对象的内容(可以理解为深拷贝)copy到elementData中。

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

add(E e) 方法

add主要的执行逻辑如下:
1)确保数组已使用长度(size)加1之后足够存下 下一个数据,如果add时原来的数组是默认数组,则将原数组扩容为10.
2)修改次数modCount 标识自增1,如果当前数组已使用长度(size)加1后的大于当前的数组长度,则调用grow方法,增长数组,grow方法会将当前数组的长度变为原来容量的1.5倍。
3)确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。
4)返回添加成功布尔值

    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) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        //数组容量不够需要调用grow进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
        int oldCapacity = elementData.length; // 旧容量
        int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为旧容量的1.5倍
        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();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

说明:正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值。

  当我们调用add方法时,实际上的函数调用如下

说明:程序调用add,实际上还会进行一系列调用,可能会调用到grow,grow可能会调用hugeCapacity。

下面通过两种方式给出调用add的例子,并分析最后的elementData数组的大小。

List<Integer> lists = new ArrayList<Integer>();
lists.add(8);

说明:初始化lists大小为0,调用的ArrayList()型构造函数,那么在调用lists.add(8)方法时,会经过怎样的步骤呢?下图给出了该程序执行过程和最初与最后的elementData的大小

  说明:我们可以看到,在add方法之前开始elementData = {};调用add方法时会继续调用,直至grow,最后elementData的大小变为10,之后再返回到add函数,把8放在elementData[0]中。

List<Integer> lists = new ArrayList<Integer>(6);
lists.add(8);

说明:调用的ArrayList(int)型构造函数,那么elementData被初始化为大小为6的Object数组,在调用add(8)方法时,具体的步骤如下:

说明:我们可以知道,在调用add方法之前,elementData的大小已经为6,之后再进行传递,不会进行扩容处理。

add(int index, E element)方法

这个方法其实和上面的add类似,该方法可以按照元素的位置,指定位置插入元素,具体的执行逻辑如下:
1)确保数插入的位置小于等于当前数组长度,并且不小于0,否则抛出异常
2)确保数组已使用长度(size)加1之后足够存下 下一个数据
3)修改次数(modCount)标识自增1,如果当前数组已使用长度(size)加1后的大于当前的数组长度,则调用grow方法,增长数组
4)grow方法会将当前数组的长度变为原来容量的1.5倍。
5)确保有足够的容量之后,使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位。
6)将新的数据内容存放到数组的指定位置(index)上。

    public void add(int index, E element) {
        rangeCheckForAdd(index);
 
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

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

说明:也是先确保容量是否足够,如果不够先进行扩容,再利用System.arraycopy()方法进行数组拷贝。

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

remove方法

根据索引remove

1)判断索引有没有越界

2)自增修改次数

3)将指定位置(index)上的元素保存到oldValue

4)将指定位置(index)上的元素都往前移动一位

5)将最后面的一个元素置空,好让垃圾回收器回收

6)将原来的值oldValue返回

    public E remove(int index) {
        // 检查索引是否合法
        rangeCheck(index);
        
        modCount++;
        E oldValue = elementData(index);
        // 需要移动的元素的个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 赋值为空,有利于进行GC
        elementData[--size] = null; 
        // 返回旧值
        return oldValue;
    }

注意:调用这个方法不会缩减数组的长度,只是将最后一个数组元素置空而已。

根据对象remove

循环遍历所有对象,得到对象所在索引位置,然后调用fastRemove方法,执行remove操作

 

    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
    }

set方法

确保set的位置小于当前数组的长度(size)并且大于0,获取指定位置(index)元素,然后放到oldValue存放,将需要设置的元素放到指定的位置(index)上,然后将原来位置上的元素oldValue返回给用户。 

    public E set(int index, E element) {
        rangeCheck(index);
 
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

get方法

返回指定位置上的元素,

        public E get(int index) {
            rangeCheck(index);
            checkForComodification();
            return ArrayList.this.elementData(offset + index);
        }

indexOf方法

说明:从头开始查找与指定元素相等的元素,注意,是可以查找null元素的,意味着ArrayList中可以存放null元素的。与此函数对应的lastIndexOf,表示从尾部开始查找。

// 从首开始查找数组里面是否存在指定元素
    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;
    }

contains方法

调用indexOf方法,遍历数组中的每一个元素作对比,如果找到对于的元素,则返回true,没有找到则返回false。

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

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

clear方法

添加操作次数(modCount),将数组内的元素都置空,等待垃圾收集器收集,不减小数组容量。

    public void clear() {
        modCount++;
 
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
 
        size = 0;
    }

trimToSize方法

1)修改次数加1

2)将elementData中空余的空间(包括null值)去除,例如:数组长度为10,其中只有前三个元素有值,其他为空,那么调用该方法之后,数组的长度变为3.

    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

clone方法

/**
     * 实现的有Cloneable接口,深度复制:对拷贝出来的 ArrayList 对象的操作,不会影响原来的 ArrayList
     * @return 一个克隆的 ArrayList 实例(深度复制的结果)
     */
    public Object clone() {
        try {
            // Object 的克隆方法:会复制本对象及其内所有基本类型成员和 String 类型成员,但不会复制对象成员、引用对象
            ArrayList<?> v = (ArrayList<?>) super.clone();
            // 对需要进行复制的引用变量,进行独立的拷贝:将存储的元素移入新的 ArrayList 中
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

ArrayList的迭代器

/**
     * 返回从指定索引开始到结束的带有元素的list迭代器
     */
    public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }

    /**
     * 返回从0索引开始到结束的带有元素的list迭代器
     */
    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

    /**
     * 以一种合适的排序返回一个iterator到元素的结尾
     */
    public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * Itr是AbstractList.Itr的优化版本
     * 为什么会报ConcurrentModificationException异常?
     * 1. Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。
     * 2. Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,
     * 这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,
     * 3. 所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。
     * 4. 所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。
     * 但你可以使用 Iterator 本身的方法 remove() 来删除对象,
     * 5. Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。
     */
    private class Itr implements Iterator<E> {
        int cursor;       // 下一个元素返回的索引
        int lastRet = -1; // 最后一个元素返回的索引  -1 if no such
        int expectedModCount = modCount;

        /**
         * 是否有下一个元素
         */
        public boolean hasNext() {
            return cursor != size;
        }

        /**
         * 返回list中的值
         */
        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;//i当前元素的索引
            if (i >= size)//第一次检查:角标是否越界越界
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)//第二次检查,list集合中数量是否发生变化
                throw new ConcurrentModificationException();
            cursor = i + 1; //cursor 下一个元素的索引
            return (E) elementData[lastRet = i];//最后一个元素返回的索引
        }

        /**
         * 移除集合中的元素
         */
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                //移除list中的元素
                ArrayList.this.remove(lastRet);
                //由于cursor比lastRet大1,所有这行代码是指指针往回移动一位
                cursor = lastRet;
                //将最后一个元素返回的索引重置为-1
                lastRet = -1;
                //重新设置了expectedModCount的值,避免了ConcurrentModificationException的产生
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        /**
         * jdk 1.8中使用的方法
         * 将list中的所有元素都给了consumer,可以使用这个方法来取出元素
         */
        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        /**
         * 检查modCount是否等于expectedModCount
         * 在 迭代时list集合的元素数量发生变化时会造成这两个值不相等
         */
        final void checkForComodification() {
            //当expectedModCount和modCount不相等时,就抛出ConcurrentModificationException
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
      /*------------------------------------- Itr 结束 -------------------------------------------*/
    /**
     * AbstractList.ListItr 的优化版本
     * ListIterator 与普通的 Iterator 的区别:
     * - 它可以进行双向移动,而普通的迭代器只能单向移动
     * - 它可以添加元素(有 add() 方法),而后者不行
     */
    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        /**
         * 是否有前一个元素
         */
        public boolean hasPrevious() {
            return cursor != 0;
        }

        /**
         * 获取下一个元素的索引
         */
        public int nextIndex() {
            return cursor;
        }

        /**
         * 获取 cursor 前一个元素的索引
         * - 是 cursor 前一个,而不是当前元素前一个的索引。
         * - 若调用 next() 后马上调用该方法,则返回的是当前元素的索引。
         * - 若调用 next() 后想获取当前元素前一个元素的索引,需要连续调用两次该方法。
         */
        public int previousIndex() {
            return cursor - 1;
        }

        /**
         * 返回 cursor 前一元素
         */
        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)//第一次检查:索引是否越界
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)//第二次检查
                throw new ConcurrentModificationException();
            cursor = i;//cursor回移
            return (E) elementData[lastRet = i];//返回 cursor 前一元素
        }

        /**
         * 将数组的最后一个元素,设置成元素e
         */
        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                //将数组最后一个元素,设置成元素e
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        /**
         * 添加元素
         */
        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;//当前元素的索引后移一位
                ArrayList.this.add(i, e);//在i位置上添加元素e
                cursor = i + 1;//cursor后移一位
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }
    /*------------------------------------- ListItr 结束 -------------------------------------------*/

Iterator和ListIterator的区别:

Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。

Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。

ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引 

遍历ArrayList的三种方式

           //第一种遍历方式:利用下标遍历
          System.out.print("for循环的遍历方式:");
          for (int i = 0; i < lists.size(); i++) {
              System.out.print(lists.get(i));
          }
          System.out.println();
          
         //第二种遍历方式:利用foreach遍历
         System.out.print("foreach的遍历方式:");
         for (Integer list : lists) {
             System.out.print(list);
         }
         System.out.println();
         
         //第三种遍历方式:利用迭代器遍历
         System.out.print("Iterator的遍历方式:");
         for (Iterator<Integer> list = lists.iterator(); list.hasNext();) {
             System.out.print(list.next());
        }

System.arraycopy 方法
参数                   说明
src                    原数组
srcPos             原数组
dest                目标数组
destPos     目标数组的起始位置
length      要复制的数组元素的数目

Arrays.copyOf方法
original - 要复制的数组 
newLength - 要返回的副本的长度 
newType - 要返回的副本的类型

其实Arrays.copyOf底层也是调用System.arraycopy实现的源码如下:

    //基本数据类型(其他类似byte,short···)
    public static int[] copyOf(int[] original, int newLength) {
        int[] copy = new int[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

Vector & ArrayList 区别
1.Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。 
2.当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍(默认情况),而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。并且Vector可以指定扩容大小

小结

  • ArrayList自己实现了序列化和反序列化的方法,因为它自己实现了 private void writeObject(java.io.ObjectOutputStream s)和 private void readObject(java.io.ObjectInputStream s) 方法
  • ArrayList基于数组方式实现,无容量的限制(会扩容)
  • 添加元素时可能要扩容(所以最好预判一下),删除元素时不会减少容量(若希望减少容量,trimToSize()),删除元素时,将删除掉的位置元素置为null,下次gc就会回收这些元素所占的内存空间。
  • 线程不安全
  • add(int index, E element):添加元素到数组中指定位置的时候,需要将该位置及其后边所有的元素都整块向后复制一位
  • get(int index):获取指定位置上的元素时,可以通过索引直接获取(O(1))
  • remove(Object o)需要遍历数组
  • remove(int index)不需要遍历数组,只需判断index是否符合条件即可,效率比remove(Object o)高
  • contains(E)需要遍历数组
  • 使用iterator遍历可能会引发多线程异常
     
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值