深入学习Java之ArrayList

来源:互联网 发布:单片机中断程序编写 编辑:程序博客网 时间:2024/06/18 13:59

深入学习Java之ArrayList

前言

对于Java的容器类,虽然之前一直有在使用,不过使用归使用,很多的原理本质上还是不懂,比如说什么时候进行扩容等,乘着最近有空,每天抽出点时间来重新学习下容器类,主要包括了(Collection、Map),其中Collection主要包括List、Set、Queue等,本小节主要来学习List中的ArrayList

ArrayList的继承结构

首先从宏观上来了解ArrayList的结构,然后再从源码的角度来学习。

ArrayList的继承结构如下所示ArrayList继承结构

从上图中可以看到,ArrayList继承自AbstractList,并且实现了RandomAccess、Clonable、Serializable接口,而其中的AbstractList又实现了AbstractCollection接口,并且继承了List接口,AbstractCollection实现了Collection接口,List继承了Collection接口,Collection接口又继承了Iterable接口。

接下来我们根据上图,从上到下来逐个类、接口来学习

首先是Iterable接口

Iterable接口

从上图中可以看到,Iterable接口中主要提供了iterator方法,用于返回一个Iterator对象,主要用于遍历容器(foreach)

接下来是Collection接口

Collection接口

从上图中可以看到,Collection接口基本上定义了操作容器的基本方法,包括了获取容器大小的size方法,判断容器是否为空的isEmpty方法,判断容器是否包含某个元素的contains方法,将容器的内容装换为数组的toArray方法,添加元素的add方法,移除元素的remove方法等,这些方法都是操作容器的最基本的方法,在对应的实现类中,不同的容器实现类将根据自身的特性,对不同的方法进行相应的实现。

接下来是AbstractCollection接口,AbstractCollection是一个抽象类,主要实现了Collection接口中的部分通用方法,具体如下代码所示

    public boolean contains(Object o) {        // 获得迭代器对象        Iterator<E> it = iterator();        // 判断对象是否是NULL,如果是NUll,则查看容器中是否        // 有对象也是NULL        if (o==null) {            while (it.hasNext())                if (it.next()==null)                    return true;        } else {            // 如果不是NULL,则查看容器中是否包含该方法            while (it.hasNext())                if (o.equals(it.next()))                    return true;        }        return false;    }
    // 原理同上    public boolean remove(Object o) {        Iterator<E> it = iterator();        if (o==null) {            while (it.hasNext()) {                if (it.next()==null) {                    it.remove();                    return true;                }            }        } else {            while (it.hasNext()) {                if (o.equals(it.next())) {                    it.remove();                    return true;                }            }        }        return false;    }
    // 将容器转化为数组    public Object[] toArray() {        // 默认大小为容器中元素的个数        Object[] r = new Object[size()];        Iterator<E> it = iterator();        for (int i = 0; i < r.length; i++) {            // 当发现容器中的元素个数没有那么多时,将数组进行缩减            if (! it.hasNext()) // fewer elements than expected                return Arrays.copyOf(r, i);            r[i] = it.next();        }        // 当发现容器中个数多余size时,进行扩容,并将剩余元素添加到数组中        return it.hasNext() ? finishToArray(r, it) : r;    }    // 将容器中剩余的元素加入到数组中    private static <T> T[] finishToArray(T[] r, Iterator<?> it) {        int i = r.length;        while (it.hasNext()) {            int cap = r.length;            if (i == cap) {                // 当发现数组容量不足时,进行扩容,扩容后大小为原来的大小+原来大小的1/2 + 1                int newCap = cap + (cap >> 1) + 1;                // 对扩容后的大小进行判断,防止容量过大                // 其中的 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;                // 也就是如果扩容后的大小大于Integer.MAX_VALUE - 8,则进行                // 容量判断,防止其整数溢出                if (newCap - MAX_ARRAY_SIZE > 0)                    newCap = hugeCapacity(cap + 1);                r = Arrays.copyOf(r, newCap);            }            r[i++] = (T)it.next();        }        // 如果数组容量由空余,则进行缩减至数组容量大小        return (i == r.length) ? r : Arrays.copyOf(r, i);    }    // 限制数组容量    private static int hugeCapacity(int minCapacity) {        // 如果新容量太大,造成整数溢出,则抛出异常        if (minCapacity < 0) // overflow            throw new OutOfMemoryError                ("Required array size too large");        // 如果大于允许的最大容量,则将其限制在Integer.MAX_VALUE        // 否则则将其限制在MAX_ARRAY_SIZE        return (minCapacity > MAX_ARRAY_SIZE) ?            Integer.MAX_VALUE :            MAX_ARRAY_SIZE;    }

当然,AbstractCollection中还有其他一些方法,不过,由于在具体的实现类中,可能对应的实现不同,所以这里就不进行其源码的研究,等到具体的实现类中再进行研究

接下来是List接口

由于List接口继承自Collection接口,所以Collection中有的方法List也自然有,不过,除此之外,List还增加了几个新的方法,主要用于针对List的操作List的新增方法

从上图中可以看到,主要是获取指定位置的get方法,将某个位置的元素设置为新的值的set方法,获取某个元素在List中的索引的indexOf、LastIndexOf方法,以及List增加的用于获取新的迭代器的ListIterator方法

接下来是AbstractList,正如AbstractCollection之于Collection,AbstractList是List的实现,主要实现了几个常用的方法,这里同样不进行展开

从ArrayList的结构图中可以看到,ArrayList除了AbstractList和实现了List接口之外,还实现了RandomAccess、Cloneable、Serializable接口,其中RandomAccess、Cloneable和Serializable接口均是标记接口,RandomAccess用于标记ArrayList支持随机访问,Cloneable接口用于标记ArrayList支持克隆,Serializable接口用于标记ArrayList支持序列化

ArrayList的源码剖析

上面从宏观上了解了ArrayList的继承结构,总体把握了ArrayList的方法之后,接下来我们来详细地学习ArrayList的源码

ArrayList的组成

transient Object[] elementData;

可以看到,ArrayList的底层实现其实就是一个Object数组,这也是为什么ArrayList称之为ArrayList的原因了

接下来来学习ArrayList的构造方法

    // 带初始容量的初始化方法    public ArrayList(int initialCapacity) {        // 如果初始容量大于0,则使用该容量进行创建数组        if (initialCapacity > 0) {            this.elementData = new Object[initialCapacity];        // 如果容量是0,则使用默认的的空数组对象,其中的EMPTY_ELEMENTDATA定义如下        // private static final Object[] EMPTY_ELEMENTDATA = {};         } else if (initialCapacity == 0) {            this.elementData = EMPTY_ELEMENTDATA;        // 如果容量小于0,则抛出异常        } else {            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);        }    }    // 无参构造方法,也是默认的构造方法    public ArrayList() {        // 默认也是初始化为空数组        // private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;    }    // 带一个容器对象的构造方法,主要用于将容器中的所有对象来初始化ArrayList对象     public ArrayList(Collection<? extends E> c) {        // 将Collection转化为数组对象        elementData = c.toArray();        // 如果传进来的Collection中有元素,则将元素添加到数组中        if ((size = elementData.length) != 0) {            // 如果转化后的数组不是Object类型,则初始化为实际类型            if (elementData.getClass() != Object[].class)                elementData = Arrays.copyOf(elementData, size, Object[].class);        // 否则将其初始化为空容量        } else {            this.elementData = EMPTY_ELEMENTDATA;        }    }

获取某个元素

    public E get(int index) {        // 对index的范围进行检查        rangeCheck(index);        return elementData(index);    }    // 对index的范围进行检查,由于对数组进行访问时,负数索引会抛出异常    // 所以这里不需要进行额外检查,但是由于此时数组的容量并不代表所有的    // 位置都是有效元素,所以需要进行检查    private void rangeCheck(int index) {        if (index >= size)            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));    }

将指定位置的元素替换为指定元素

    public E set(int index, E element) {        rangeCheck(index);        // 替换旧值并且返回旧值        E oldValue = elementData(index);        elementData[index] = element;        return oldValue;    }

添加某个元素

    // 默认添加在最后面    public boolean add(E e) {        // 进行容量检测        ensureCapacityInternal(size + 1);         elementData[size++] = e;        return true;    }    // 容量检测    private void ensureCapacityInternal(int minCapacity) {        // 如果是此时的数组的默认的空数组,则将其容量初始化为        // max(默认容量(10), 此时所需要的最小容量)        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);        }        // 判断此时容量是否足够        ensureExplicitCapacity(minCapacity);    }    private void ensureExplicitCapacity(int minCapacity) {        // 记录对ArrayList结构进行操作        modCount++;        // 如果所需要的容量大于数组此时的长度,则增长数组        if (minCapacity - elementData.length > 0)            grow(minCapacity);    }    // 动态增长,注意这里比较耗费资源    private void grow(int minCapacity) {        int oldCapacity = elementData.length;        // 大小为旧容量大小+ 1/2 旧容量大小        int newCapacity = oldCapacity + (oldCapacity >> 1);        // 如果新容量大小小于所需要的大小,则将大小设置为实际所需要的大小        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;    }

在指定位置插入元素

    public void add(int index, E element) {        rangeCheckForAdd(index);        // 同上,进行容量检测并在需要时进行动态增长        ensureCapacityInternal(size + 1);        // 将要插入的位置的元素后面所有元素后移        // 这里可以看出,ArrayList在中间插入元素是比较消耗资源的        System.arraycopy(elementData, index, elementData, index + 1,                         size - index);        elementData[index] = element;        size++;    }

添加一个容器中的所有元素

    // 基本原理同上,这里不进行过多的解析    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;    }

移除指定位置元素

    public E remove(int index) {        rangeCheck(index);        modCount++;        E oldValue = elementData(index);        int numMoved = size - index - 1;        // 如果要移出的元素不是最后一个元素,则进行紧凑        // 从这里也可以看到,ArrayList在中间移除元素是比较消耗资源的        if (numMoved > 0)            System.arraycopy(elementData, index+1, elementData, index,                             numMoved);        elementData[--size] = null; // clear to let GC do its work        return oldValue;    }

在指定位置添加所有的元素

    // 基本原理同上,不做过多解析    public boolean addAll(int index, Collection<? extends E> c) {        rangeCheckForAdd(index);        Object[] a = c.toArray();        int numNew = a.length;        ensureCapacityInternal(size + numNew);  // Increments modCount        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 != 0;    }

移除指定值的元素

    // 移除第一个匹配的值    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    }

移除所有元素

    public void clear() {        modCount++;        // 这里需要注意,由于数组中的元素是通过引用关系跟        // 具体的对象关联的,所有如果此时只是简单的移动索引        // 那么这些元素所占用的内存依旧不会被GC,因为依旧保持        // 跟数组的管理,GC回收是不会对这种对象进行回收的        // 这里写得非常棒,非常值得学习        for (int i = 0; i < size; i++)            elementData[i] = null;        size = 0;    }

查找某个元素的索引以及查找从后往前找某个元素的索引基本同AbstractCollection,这里就不进行展开了

克隆数组

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

手动触发容量检测

    public void ensureCapacity(int minCapacity) {        // 如果是默认的空数组,则容量为0,否则容量为10        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;        // 对最小容量与需要的容量进行判断,并根据需要进行容量扩展        if (minCapacity > minExpand) {            ensureExplicitCapacity(minCapacity);        }    }

手动将ArrayList中数组的大小进行确定,限制为实际的大小

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

对与ArrayList中的Iterator以及ListIterator的源码,目前还没看懂,所以暂时无法进行研究,等以后研究懂了再进行补充

总结

本小节主要从宏观上学习了ArrayList的结构,以及从ArrayList的源码中研究了ArrayList的底层实现,以及ArrayList的扩容,紧凑原理(数组的拷贝,移动),从本质上明白了为何ArrayList在中间进行插入,删除会带来严重性能的原因(数组元素的移动),加深了对ArrayList的认识。

原创粉丝点击