【Java深入】ArrayList源码剖析(一)

来源:互联网 发布:淘宝查询别人购买记录 编辑:程序博客网 时间:2024/06/06 00:40

一、ArrayList概述

1.实现原理

ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。允许重复,默认第一次插入元素时创建数组的大小为10,超出限制时会增加50%的容量,每次扩容都底层采用System.arrayCopy()复制到新的数组,初始化时最好能给出数组大小的预估值。

2.扩容:

每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。

3.同步性:

 ArrayList的用法和Vector相类似,但是Vector是一个较老的集合,具有很多缺点,不建议使用。它们的区别是:ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性,所以只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。而Vector则是线程安全的。

4.序列化

ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。

二、源码剖析(JDK8)

1.类的继承关系

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

分析:ArrayList支持泛型,它继承自AbstractList类,实现了List、RandomAccess、Cloneable、Java.io.Serializable接口。AbstractList提供了List接口的默认实现(个别方法为抽象方法)。RandomAccess是一个标记接口,接口内没有定义任何内容。对于Cloneable接口,可以调用Object.clone方法返回该对象的浅拷贝。通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。序列化接口没有方法或字段,仅用于标识可序列化的语义。

类关系图解:

这里写图片描述

注:绿色虚线表示实现的接口,黄色实线表示继承的类,绿色实线表示接口间的继承。

2.类的属性

    //序列号    private static final long serialVersionUID = 8683452581122892189L;    //默认容量    private static final int DEFAULT_CAPACITY = 10;    //一个空数组,当指定ArrayList 容量为 0 时,返回该空数组    private static final Object[] EMPTY_ELEMENTDATA = {};    //一个空数组实例,当没有指定 ArrayList 的容量时(即调用无参构造函数),返回的是该数组,数据量为 0,    //当用户第一次添加元素时,该数组将会扩容,变成默认容量为10的一个数组,通过 ensureCapacityInternal() 实现    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};    //用该数组保存数据, 该数组的长度就是ArrayList 的容量    transient Object[] elementData; // non-private to simplify nested class access    //ArrayList实际存储的数据数量    private int size;    //数组缓冲区最大存储容量,若尝试分配这个最大存储容量,可能会导致 OutOfMemoryError(当该值 > VM 的限制时)    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 

说明:DEFAULTCAPACITY_EMPTY_ELEMENTDATA与 EMPTY_ELEMENTDATA 的区别就是:前者是默认返回的,而后者是在用户指定容量为 0 时返回。

注:ArrayList只定义了两个私有属性:elementData和size,其中elementData用于存储ArrayList内的元素,而size表示它包含的元素的数量。

3.类的构造器

(1) ArrayList ( int ) 类型构造器

    //带初始容量大小的构造函数    public ArrayList(int initialCapacity) {        //若初始容量大于0,实例化数组        if (initialCapacity > 0) {            this.elementData = new Object[initialCapacity];        }        //若初始量等于0,将空数组赋给elementData        else if (initialCapacity == 0) {            this.elementData = EMPTY_ELEMENTDATA;        }        //若初始容量小于0,则抛出异常        else {            throw new IllegalArgumentException("Illegal Capacity: "+                     initialCapacity);        }    }

(2)ArrayList()无参构造器

//默认容量为10public ArrayList(){    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

(3)ArrayList(Collection< ? extends E > c)类型构造器

//创建一个包含collection的ArrayList    public ArrayList(Collection<? extends E> c) {        //集合转 Object[] 数组        elementData = c.toArray();        //将转换后的 Object[] 长度赋值给当前 ArrayList 的 size,并判断是否为 0        if ((size = elementData.length) != 0) {            if (elementData.getClass() != Object[].class)                // 若 c.toArray() 返回的数组类型不是 Object[],则利用copyof方法创建一个Object[] 数组                elementData = Arrays.copyOf(elementData, size, Object[].class);        }        else {            //c中没有元素,则换为空数组            this.elementData = EMPTY_ELEMENTDATA;        }    }

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

分析:如果newType类型为Object[].class的话,则直接创建一个长度为newLength的Object数组,否则使用Array.newInstance(Class< ?> componentType, int length)方法创建一个元素类型为newType.getComponentType() (该方法返回数组中元素的类型)类型的,长度为newLength的数组,这是一个native方法,然后使用System.arraycopy() 这个native方法将original数组中的元素copy到新创建的数组中,并返回该数组。

4.类的方法

(1)trimToSize方法(将当前容量值设为当前实际元素大小)

    //该方法手动调用,以减少空间资源浪费的目的    public void trimToSize() {        // modCount(用于记录结构性变化次数)是 AbstractList 的属性值(protected transient int modCount = 0)        modCount++;        // 当实际大小 < 数组缓冲区大小时        // 如调用默认构造函数后,刚添加一个元素,此时 elementData.length = 10,而 size = 1        // 通过这一步,可以使得空间得到有效利用,而不会出现资源浪费的情况        if (size < elementData.length) {            // 调整数组缓冲区 elementData,变为实际存储大小 Arrays.copyOf(elementData, size)            elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);        }    }

(2)ensureCapacity方法(指定 ArrayList 的容量)

    public void ensureCapacity(int minCapacity) {        // 最小扩充容量,默认是 10          int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;        // 若用户指定的最小容量 > 最小扩充容量,则以用户指定的为准,否则还是 10          if (minCapacity > minExpand) {            ensureExplicitCapacity(minCapacity);        }    }

注:ensureCapacity() 是提供给用户使用的方法,在 ArrayList 的实现中并没有使用

(3)ensureCapacityInternal方法(确定ArrarList 的容量)

    private void ensureCapacityInternal(int minCapacity) {        // 若 elementData == {},则取 minCapacity 为 默认容量和参数 minCapacity 之间的最大值        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);        }        ensureExplicitCapacity(minCapacity);    }

注:该方法用于内部优化,保证空间资源不被浪费:尤其在 add() 方法添加时起效

(4)ensureExplicitCapacity方法(明确 ArrayList 的容量 )

    private void ensureExplicitCapacity(int minCapacity) {        // 将“修改统计数”+1,该变量主要是用来实现fail-fast机制的        modCount++;        // 防止溢出代码:确保指定的最小容量 > 数组缓冲区当前的长度        if (minCapacity - elementData.length > 0)            grow(minCapacity);    }

注:该方法与ensureCapacityInternal方法类似,用于内部优化,保证空间资源不被浪费:尤其在 add() 方法添加时起效 。

(5)grow方法(用来扩容)

    private void grow(int minCapacity) {        // 防止溢出代码        int oldCapacity = elementData.length;        // 扩容为原来的1.5倍        int newCapacity = oldCapacity + (oldCapacity >> 1);        if (newCapacity - minCapacity < 0)                               newCapacity = minCapacity;        // 若 newCapacity 大于最大存储容量,则进行大容量分配        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);    }

注:该方法确保 ArrayList 至少能存储 minCapacity 个元素,扩容计算:newCapacity = oldCapacity + (oldCapacity >> 1),其中运算符 >> 是带符号右移.

其中大容量分配的hugeCapacity方法源码为:

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

(6)size方法(获取该 list 所实际存储的元素个数)

public int size() {        return size;    }

(7)isEmpty方法(判断 list 是否为空 )

    public boolean isEmpty() {        // 利用size 是否为 0来判断list是否为空        return size == 0;                     } 

(8)contains方法(判断该 ArrayList 是否包含指定对象(Object 类型))

    public boolean contains(Object o) {        // 由索引值判断,大于等于 0 就返回true        return indexOf(o) >= 0;    }

注意:该方法是面对抽象编程,向上转型是安全的,索引号是从 0 开始计数的

(9)indexOf方法(返回元素最先出现的索引位置)

    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;Arrays 工具类提供了二分搜索,但没有提供顺序查找

(10)lastIndexOf方法( 返回此列表中最后一次出现的指定元素的索引 )

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

注:此方法为反向查找(从数组末尾向开始查找)

(11)clone方法(深拷贝)

public Object clone() {        try {            //调用Object 的克隆方法            ArrayList<?> v = (ArrayList<?>) super.clone();            // 对需要进行复制的引用变量,进行独立的拷贝:将存储的元素移入新的 ArrayList 中            v.elementData = Arrays.copyOf(elementData, size);            v.modCount = 0;            return v;        } catch (CloneNotSupportedException e) {            throw new InternalError(e);        }    }

注:此克隆方法(深拷贝)对拷贝出来的 ArrayList 对象的操作,不会影响原来的 ArrayList,而Object 的克隆方法(浅拷贝):会复制本对象及其内所有基本类型成员和 String 类型成员,但不会复制对象成员、引用对象。

若你不了解深拷贝与浅拷贝,请点击 深拷贝与浅拷贝详解

(12)toArray方法

  • 无参(返回 ArrayList 的 Object 数组 )
    public Object[] toArray() {        return Arrays.copyOf(elementData, size);    }

注:对返回的该数组进行操作,不会影响该 ArrayList(相当于分配了一个新的数组),该操作是安全的,元素存储顺序与 ArrayList 中的一致。

  • 带参数(返回 ArrayList 元素组成的数组 )
    @SuppressWarnings("unchecked")    public <T> T[] toArray(T[] a) {        // 若数组a的大小 < ArrayList的元素个数,则新建一个T[]数组,数组大小是"ArrayList的元素个数",并将“ArrayList”全部拷贝到新数组中        if (a.length < size)            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;    }

注:若 a 中本来存储有元素,则 a 会被 list 的元素覆盖,且 a[list.size] = null

(13)elementData方法(返回在索引为 index 的元素)

@SuppressWarnings("unchecked")    E elementData(int index) {        return (E) elementData[index];    }

(14)get方法(获取指定位置的元素)

    public E get(int index) {        // 检查是否越界        rangeCheck(index);        // 随机访问        return elementData(index);            } 

其中rangeCheck方法源码如下:

private void rangeCheck(int index) {        if (index >= size)            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));    }

注:rangeCheck方法并没有检查 index 是否合法,例如:当 index< 0
通常由数组类型自己检查。

(15)set方法(设置 index 位置元素的值)

public E set(int index, E element) {        //  数组越界检查        rangeCheck(index);        // 取出旧值        E oldValue = elementData(index);        // 替换成新值        elementData[index] = element;                   return oldValue;    }

。。。。。。

注:由于篇幅有限,其余方法的源码剖析请参考下一篇博文





本人才疏学浅,若有错,请指出
谢谢!

0 0
原创粉丝点击