ArrayList

ArrayList介绍

ArrayList简介

  1. ArrayList是一个数组队列,至关于动态数组。与Java中的数组相比,它的容量能动态增加。它继承于AbstractList,实现了List,RandomAccess,Cloneable,java.io.Serializable这些接口。
  2. ArrayList与Collection的关系以下图,实现表明继承,虚线表明实现接口:
  3. ArrayList继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
  4. ArrayList实现了RandmoAccess接口,即提供了随机访问的功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,咱们便可以经过元素的序号快速获取元素对象;这就是快速随机访问。
  5. ArrayList实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
  6. ArrayList实现了java.io.Serializable接口,这意味着ArrayList支持序列化,能经过序列化去传输。

注意:ArrayList中的操做不是线程安全的!因此,建议在单线程中使用,多线程状况下能够选择CopyOnWriteArrayList或者使用Collections中的synchronizedList方法将其包装成一个线程安全的List。java

ArrayList的API

// Collection中定义的API
boolean             add(E object)
boolean             addAll(Collection<? extends E> collection)
void                clear()
boolean             contains(Object object)
boolean             containsAll(Collection<?> collection)
boolean             equals(Object object)
int                 hashCode()
boolean             isEmpty()
Iterator<E>         iterator()
boolean             remove(Object object)
boolean             removeAll(Collection<?> collection)
boolean             retainAll(Collection<?> collection)
int                 size()
<T> T[]             toArray(T[] array)
Object[]            toArray()
// AbstractCollection中定义的API
void                add(int location, E object)
boolean             addAll(int location, Collection<? extends E> collection)
E                   get(int location)
int                 indexOf(Object object)
int                 lastIndexOf(Object object)
ListIterator<E>     listIterator(int location)
ListIterator<E>     listIterator()
E                   remove(int location)
E                   set(int location, E object)
List<E>             subList(int start, int end)
// ArrayList新增的API
Object               clone()
void                 ensureCapacity(int minimumCapacity)
void                 trimToSize()
void                 removeRange(int fromIndex, int toIndex)

ArrayList源码分析

属性

ArrayList的主要属性以下代码所示:git

//序列化id
private static final long serialVersionUID = 8683452581122892189L;
//容器默认初始化大小
private static final int DEFAULT_CAPACITY = 10;
//一个空对象
private static final Object[] EMPTY_ELEMENTDATA = {};
//一个空对象,若是使用默认构造函数建立ArrayList,则默认对象内容是该值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//ArrayList存放对象的容器,后面的添加、删除等操做都是基于该属性来进行操做
transient Object[] elementData;
//当前列表已使用的长度
private int size;
//数组最大长度(2147483639),这里为何是Integer.MAX_VALUE - 8是由于有些虚拟机在数组中保留了一些头部信息,防止内存溢出
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//这个是从AbstractList继承过来的,表明ArrayList集合修改的次数
protected transient int modCount = 0;

关于Java中transient关键字的解释:github

 咱们都知道一个对象只要实现了Serilizable接口,这个对象就能够被序列化,java的这种序列化模式为开发者提供了不少便利,咱们能够没必要关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的全部属性和方法都会自动序列化。api

 然而在实际开发过程当中,咱们经常会遇到这样的问题,这个类的有些属性须要序列化,而其余属性不须要被序列化,打个比方,若是一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不但愿在网络操做(主要涉及到序列化操做,本地序列化缓存也适用)中被传输,这些信息对应的变量就能够加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。数组

总之,java 的transient关键字为咱们提供了便利,你只须要实现Serilizable接口,将不须要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。缓存

构造函数

无参构造函数

若是不传入参数,则使用默认无参构造方法建立ArrayLisy对象,以下:安全

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

注意:此时咱们建立的ArrayList对象中的elementData中的长度是0,size是0,当进行第一次add的时候,elementDate将会变成默认的长度:10。网络

带int类型的构造函数

若是传入参数,则表明指定ArrayList的初始数组长度;传入参数若是是大于0,则使用用户的参数初始化;若是参数等于0,则用内部的空对象EMPTY_ELEMENTDATA的地址直接赋值给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);
        }
    }

带Collection对象的构造函数

  1. 将Collection对象转换成数组,而后将数组的地址赋值给elementData。
  2. 更新size的值,若是size的值等于0直接将内部空对象EMPTY_ELEMENTDATA的地址赋值给elementData。
  3. 若是size的值大于0,则执行Arrays.copy方法,把Collection对象的内容copy(能够理解为深拷贝)到elementData中,而且这些元素是按照该collection的迭代器返回它们的顺序排列的。
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;
        }
    }

这里介绍下System.arraycopyArrays.copy方法,由于后分析源码时会常常用到。app

System.arraycopy方法:它就是从指定的源数组将元素中复制到目标数组,复制从指定的位置开始,到设定的复制长度结束,而后从目标数组的指定起始位置依次插入。

// src 源数组
    // srcPos 源数组要复制的起始位置
    // dest 要赋值到的目标数组
    // destPos 目标数组放置的起始位置
    // length 复制的长度
    // 使用了native关键字,说明调用的是其余语言写的底层函数
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

Arrays.copy方法:它新建了一个数组而且将原数组的内容拷贝到长度为newLength的新数组中,而且返回该新数组。

// original 要复制的数组
    // newLength 要返回副本的长度
    // newwType 要返回的副本类型
    // 内部调用了System.arraycopy方法
    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;
    }

区别:

  1. System.arraycopy须要目标数组,将原数组拷贝到你本身定义的数值里,并且能够选择拷贝的起点和长度以及放入新数组中的位置。
  2. Arrays.copyof是系统自动在内部新建一个数组,调用System.arraycopy将原数组的内容拷贝到长度为newLength的新数组中,并返回新建的数组。

添加元素

ArrayList提供了add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)、set(int index, E element)这个五个方法来实现ArrayList增长。

add(E e)方法
//官方解释:将指定的元素追加到列表(elementData)的末尾
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

接下来咱们看ensureCapacityInternal方法,以及它内部调用的方法。

//参数值实际是size+1
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
//这个方式是判断当前数组是不是个空数组,若是是就返回默认长度10,不然就返回size+1;也就是说若是你是用无参构造函数初始化ArrayList,那么在第一次调用add方法时,默认长度会变成10
  private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
//这个方法首先将集合修改次数加1,而后判断数组的长度是否能够存入下一个元素,若是长度不够会调用grow方法进行扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
//这个方法首先定义数组新的长度为原来数组长度的1.5倍,若是新长度减去所需数组的最小长度小于0,那么新长度就等于所需数组最小长度;再下面的判断是若是新长度大于MAX_ARRAY_SIZE(ArrayList内部定义MAX_ARRAY_SIZE的值是:2147483639)就调用hugeCapacity方法,最后调用Arrays.copyOf将扩容后的新数组地址赋值给elementData
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
//若是扩容长度超过MAX_ARRAY_SIZE,则设置长度为Integer.MAX_VALUE,但不是能百分百成功的,这取决于虚拟机。(若是咱们能够在某些虚拟机上能够避免OutOfMemory,咱们将另外分配Integer.MAX_VALUE,若是你很幸运(取决于虚拟机),咱们将成功)
    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方法的逻辑:

  1. 确保数组已使用长度(size)加1后能够存入下一个元素。

  2. 修改次数modCount标识自增1,若是当前数组已使用长度+1后大于当前数组长度,则调用grow方法,扩容数组,grow方法会将当前数组的长度变为原来容量的1.5倍。

  3. 确保新加的元素有地方存储后,则将新元素添加到位于size++的位置上。

  4. 返回添加成功的布尔值。

add(int index, E element)

这个方法和上面的add类型,该方法能够按照元素的位置,指定新元素位置插入。

public void add(int index, E element) {
        //判断索引位置是否正确
        rangeCheckForAdd(index);
        //扩容检测
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //对源数组进行复制处理(位移),从index + 1到size - index
        //即向后移动位于当前位置和后面的元素
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //在指定的位置赋值
        elementData[index] = element;
        size++;
    }

该方法首先调用rangeCheckForAdd方法判断指定的位置小于当前数组的长度而且大于0,不然抛出异常。

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

第二步调用的ensureCapacityInternal方法和上面的add方法逻辑同样。

第三步调用System.arraycopy方法把指定下标以及后面的元素所有日后移一位

最后将新的元素放到指定位置(index)上,并将size+1。

addAll(Collection<? extends E> c)
//按照指定的Collection迭代器所返回的顺序,依次插入到列表尾部。
    public boolean addAll(Collection<? extends E> c) {
        // 将c转换为数组
        Object[] a = c.toArray();
        int numNew = a.length;
        //扩容处理,大小为size + numNew
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

该方法首先传过来的Collection集合转换为数组,而后作扩容处理,接着使用System.arraycopy把转换后的数组复制到列表尾部。

addAll(int index, Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c) {
        //判断索引位置是否正确
        rangeCheckForAdd(index);
        // 将c转换为数组
        Object[] a = c.toArray();
        int numNew = a.length;
        //扩容处理,大小为size + numNew
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //若是插入索引小于列表长度,则将当前索引等于index和大于index的元素日后移numMoved个位置
        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 != 80;
    }
set(int index, E element)
public E set(int index, E element) {
        //判断插入位置是否正确,若是大于列表长度会抛出异常
        rangeCheck(index);
        //获取插入位置的当前元素
        E oldValue = elementData(index);
        //将新的元素替换当前插入位置的元素
        elementData[index] = element;
        //返回插入位置老的值
        return oldValue;
    }

删除元素

ArrayList提供了外界remove(int index)、remove(Object o)、removeAll(Collection<?> c)、clear()四个方法进行元素的删除。

remove(int index) //移除指定位置上的元素
public E remove(int index) {
        //判断删除位置是否正确,若是大于列表长度会抛出异常
        rangeCheck(index);
        //将集合修改次数加1
        modCount++;
        //获取当前删除位置上的元素
        E oldValue = elementData(index);
        //判断是否删除的是最后一个元素,若是不是将删除位置后的元素向左移numMoved个位置
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //将列表最后的元素置为null,等待垃圾收集器收集
        elementData[--size] = null; // clear to let GC do its work
        //返回删除位置老的值
        return oldValue;
    }
remove(Object o) //移除指定元素
public boolean remove(Object o) {
        //由于ArrayList容许存在null,因此须要进行null判断
        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) {
        //将集合修改次数加1      
        modCount++;
        //判断是否删除的是最后一个元素,若是不是将删除位置后的元素向左移numMoved个位置
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //将列表最后的元素置为null,等待垃圾收集器收集
        elementData[--size] = null; // clear to let GC do its work
    }
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 {
            //遍历数组,并检查这个集合是否包含对应的值,移动要保留的值到数组前面,w最后值为要保留的元素的数量
            //若保留,就将相同的元素移动到前段;不删除,就将不一样元素移动到前段
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // 确保异常抛出前的部分能够完成指望的操做,而被遍历的部分会被接到后面
            //r不等于size表示可能出错了
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            //若是w等于size,表示所有元素都保留了,因此也就没有删除操做发生,因此会返回false;反之,返回true,并更改数组
            //而w不等于size的时候,即便try块抛出异常,也能正确处理异常抛出前的操做,由于w始终为要保留的前段部分的长度,数组也不会所以乱序
            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;
    }
clear() //清空ArrayList内的全部元素,不减少数组容量
public void clear() {
        //将集合修改次数加1  
        modCount++;
        //循环将列表中的全部元素置为null,等待垃圾收集器收集
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        //将列表长度设为0
        size = 0;
    }

查找元素

ArrayList提供了get(int index)用读取ArrayList中的元素。因为ArrayList是动态数组,因此咱们彻底能够根据下标来获取ArrayList中的元素,并且速度还比较快。

public E get(int index) {
        //判断删除位置是否正确,若是大于列表长度会抛出异常
        rangeCheck(index);
        //直接返回列表中下标等于index的元素
        return elementData(index);
    }

判断元素是否存在列表中

ArrayList提供了contains(Object o)用于判断元素是否存在于列表中。

注意:contains方法会遍历ArrayList。

public boolean contains(Object o) {
        //调用indexOf方法判断须要查找的元素在列表中的下标是否大于等于0,小于0则不存在
        return indexOf(o) >= 0;
    }
public int indexOf(Object o) {
        //由于ArrayList容许存在null,因此须要进行null判断
        if (o == null) {
            //遍历列表,若是列表存在null值的元素,直接返回其下标位置
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            //遍历列表,使用equals判断是否有相等的元素,有的话直接返回其下标位置
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        //列表中不能存在传进来的元素,返回-1
        return -1;
    }

最小化ArrayList的实际存储量

ArrayList提供了trimToSize()方法用于将底层数组的容量调整为当前列表保存的实际元素的大小

public void trimToSize() {
        //将集合修改次数加1
        modCount++;
        //若是当前ArrayList的实际长度小于列表的长度,将列表超过size后的空余的空间(包括null值)去除,调用Arrays.cppyof方法拷贝elementData,长度为size
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

截取ArrayList部份内容

ArrayList提供了subList(int fromIndex, int toIndex)方法来实现部分数据的截取。

能够从源码中看到实际上是建立了一个SubList的内部对象,能够理解为是返回当前ArrayList的部分视图,其实指向的存放数据的仍是一个地方。若是修改了subList返回的内容的话,原来的内容也会被修改。

public List<E> subList(int fromIndex, int toIndex) {
        //检查须要截取的下标位置是否正确
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

其它方法

//判断ArrayList是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    //反向查找元素位置,与上述的indexOf相反
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

    //将元素所有拷贝到v中
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            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拷贝后的Object数组
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    //返回ArrayList的模板数组。所谓模板数组,便可将T设置为任意数据类型
    public <T> T[] toArray(T[] a) {
        //若a的长度小于ArrayList中的元素个数,返回拷贝了ArrayList中所有元素的新数组
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        //若a的长度大于等于ArrayList中的元素个数,则将ArrayList中的元素所有拷贝到a中
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

    //将ArrayList中的元素写入到输入流中,先写容量,在写元素
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    //从输入流中读取数据到elementData中,同样是先读容量,再读数据
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            int capacity = calculateCapacity(elementData, size);
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

小结

  • ArrayList本身实现了序列化和反序列化,由于它实现了writeObject和readObject方法。
  • ArrayList基于数组实现,会自动扩容。
  • 添加元素时会本身判断是否须要扩容,最好指定一个大概的大小,防止后面屡次扩容带来的内存消耗;删除元素时不会减小容量,删除元素时,将删除掉的位置元素置为null,下次gc就会自动回收这些元素所占的空间。
  • ArrayList是线程不安全的。
  • 使用iterator遍历可能会引起多线程异常。

文章做者:leisurexi

新博客地址:https://leisurexi.github.io/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文连接及做者。

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息