深入学习Java之ArrayList

深入学习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的认识。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值