ArrayList的底层源码剖析

    最近也是回去看了看ArrayList相关的源码,觉得受益匪浅。还是写篇博客记录一下。
    从源码的开头头看,ArrayList定义了几个属性。

private static final int DEFAULT_CAPACITY = 10;

private static final Object[] EMPTY_ELEMENTDATA = {};

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

transient Object[] elementData; 

private int size;

    我们可以看到其中有一个私有属性DEFAULT_CAPACITY,表示数组的初始容量是10。两个空数组EMPTY_ELEMENTDATA、DEFAULTCAPACITY_EMPTY_ELEMENTDATA,至于有什么区别,下面会讲到。elementData数组是真正存放数据的数组。size属性是记录elementData数组的长度。

    那EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA两个数组有什么区别呢?
    我们先看看ArrayList的有参和无参的构造方法。

	//有参构造
    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);
        }
    }
	//无参构造
	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    首先我们看有参构造,传入一个initialCapacity参数,表示定义的ArrayList的容量大小。如果initialCapacity大于0,直接实例化一个initialCapacity大小的object数组。如果initialCapacity等于0,那就把elementData数组实例化为EMPTY_ELEMENTDATA空数组。
    然后我们看一下无参构造,直接就是把elementData数组实例化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组。
    所以EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA两个空数组的区别就是:一个是有参构造是参数为0的时候实例化的,一个是无参构造时实例化的。

    下面我们看一下ArrayList的常用方法的底层实现。

    add方法

    下面我们具体看一下add方法的具体实现。
    我们可以看到add方法内部调用了ensureCapacityInternal方法,使用ensureCapacityInternal方法对判断数组是否要进行扩容,要的话就扩容原数组。然后给原来数组的最后一个元素后面的一个元素赋值。

    下面我们具体看一下ensureCapacityInternal方法的具体实现。

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    ensureCapacityInternal方法中先判断你现在elementData数组是否为空数组,如果时空数组,那就看看传入的size + 1是否比数组初始容量大。选择较大的一方作为数组容量。然后内部又调用了ensureExplicitCapacity方法。

    下面我们具体看一下ensureExplicitCapacity方法的具体实现。

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    modCount保存的是ArrayList结构的修改次数,一般当add、remove时会增加。在下面迭代的时候,会对它有详细的介绍。
    ensureExplicitCapacity方法中生成的目标数组大小和原来使用的数组大小进行了比较,当目标数组比原来的数组容量要大时,这时候就要使用grow方法扩容。

    下面我们具体看一下grow方法的具体实现。

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

    在grow方法中,我们可以看到使用oldCapacity来记录原来数组的长度,用newCapacity记录扩容后数组的长度,而扩容后的数组长度是原来数组长度的1.5倍。然后判断扩容后的数组是否比目标数组大。最后使用Arrays工具类把旧数组的数据加到新数组里面。

    remove方法
    remove方法有两个多态实现,一个是通过int类型下标删除,一个是通过object元素匹配删除。
    下面我们具体看一下通过index下标的remove方法的具体实现。

    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);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

    因为是通过index下标删除元素,首先通过rangeCheck方法检查下标是否合法。remove方法会对ArrayList进行修改,所以modCount要加1。然后我们把下标元素取出来,使用arraycopy方法对,把删除元素后面的元素前移一位,返回下标元素。

    下面我们具体看一下rangeCheck方法的具体实现。

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

    rangeCheck方法就是简单的对index下标进行判断,如果下标超出了size限制,那么就报出异常。

    下面我们具体看一下通过object元素的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;
    }

    object首先判断object元素是否为空。若为空,使用for循环,遍历所有数组元素,如果数组元素为null,则使用fastRemove方法通过下标删除。若不为空,使用for循环,遍历所有数组元素,使用equals方法比较,如果相等则fastRemove通过下标删除元素。

    下面我们具体看一下通过fastRemove方法的具体实现。

    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
    }

    移除元素时,记录ArrayList的修改记录(modCount)加1。使用数组复制的方法,把删除元素的数组前移一位,原来的数组长度减一。

    ArrayList中的迭代器实现
    回到刚刚出现的modCount,这个是表示ArrayList的修改次数记录。那么它是ArrayList的私有属性吗?不,它是ArrayList的父类AbstractList的保护属性。初始值为0。

protected transient int modCount = 0;

    同时在ArrayList中是没有获得迭代器的方法,获得迭代器的方法在它的父类AbstractList中有。

public Iterator<E> iterator() {
        return new Itr();
}

    在ArrayList的内部有一个内部类Itr,继承了Iterator接口,这个就是ArrayList的迭代器的具体实现类。

private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

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

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

    其中Itr这个迭代器内部类中有三个私有属性,cursor表示下一个元素下标,初始值为0;lastRet表示当前元素下标,初始值为-1;expectedModCount记录迭代器使用时,ArrayList当前的使用次数。
    其中checkForComodification方法就是检测在迭代当中是否有其他线程修改了ArrayList集合。因为ArrayList集合是支持多线程的,线程是不安全的。所以必须通过这个expectedModCount和modCount之间修改次数的比较,来判断是否有多个线程正在修改ArrayList集合。这也就是Fail-Fast 机制(快速失败机制)。
    因为有checkForComodification方法进行修改次数比较,所以在迭代的过程中,我们不能通过ArrayList的remove方法进行元素删除。但是Iterator迭代器中也有remove方法移除,使用迭代器中的方法移除元素就可以避免异常报错。但是这仅是针对单线程,当多线程同时访问时,应该使用CopyOnWriteArrayList并发容器代替ArrayList。

    ArrayList总结
1、ArrayList的底层是Object数组实现的。
2、ArrayList的add、remove、数组扩容方法底层都是通过数组复制的方法实现。
3、ArrayList底层的Object数组初始容量是10,每次数组扩容,新数组的长度是原数组的1.5倍。
4、ArrayList的remove方法不论是通过下标还是元素删除,实际上都是通过for循环根据下标删除。
5、增强for循环底层也是使用迭代器实现的,使用迭代器时,不能使用list集合中的remove,add方法,因为会改变modCount值,导致expectedModCount和modCount不相等。
6、ArrayList是支持多线程的,因此它是线程不安全的集合。

以下是重新表述后的内容: 单周期 MIPS CPU 的微程序地址转移逻辑设计:在单周期 MIPS CPU 架构中,微程序地址转移逻辑是关键部分。它负责根据当前微指令的执行情况以及 CPU 内部的各种状态信号,准确地计算出下一条微指令的地址。这一逻辑需要综合考虑指令类型、操作完成情况、是否发生异常等多种因素,以确保微程序能够按照正确的顺序和逻辑进行执行,从而实现 MIPS 指令的准确译码与控制。 MIPS 微程序 CPU 的设计:设计一款基于微程序控制的 MIPS CPU,其核心在于构建微程序控制器。该控制器通过存储微指令序列来实现对 CPU 各部件的控制。微指令中包含对数据通路操作的控制信号以及微程序地址转移信息。在设计过程中,需要精心设计微指令格式,使其能够高效地表示各种操作控制信息,同时合理安排微指令存储器的组织结构,确保微指令的快速读取与准确执行,从而实现 MIPS 指令集的完整功能。 MIPS 硬布线控制器的状态机设计:在采用硬布线控制方式的 MIPS CPU 中,状态机是控制器的核心组成部分。状态机根据输入的指令操作码、状态信号等信息,在不同的状态之间进行转换。每个状态对应着 CPU 在执行一条指令过程中的一个特定阶段,如取指、译码、执行、访存等。状态机的设计需要精确地定义各个状态的转换条件以及在每个状态下输出的控制信号,以确保 CPU 能够按照正确的时序和逻辑完成指令的执行过程。 多周期 MIPS 硬布线控制器 CPU 设计(排序程序):设计一款多周期 MIPS 硬布线控制器 CPU,用于运行排序程序。在这种设计中,CPU 的每个指令执行周期被划分为多个子周期,每个子周期完成指令执行过程中的一个特定操作。硬布线控制器根据指令操作码和当前周期状态,生成相应的控制信号来协调 CPU 数据通路的操作。针对排序程序的特点,需要优化控制器的设计,合理安排指令执行的周期划分
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值