ArrayList源码学习总结

内部结构

ArrayList内部实现是一个Object数组,这算是一个常识了;下文中所说数组如无特殊说明,一般是指此数组。

add方法

新增元素前需要先扩容,扩容机制很简单,如果长度够则使用原来的数组,如果数组长度不够则扩大1.5倍,还不够则直接扩大到所需长度 。

特别注意,扩容是把原来数组的内容拷贝到一个新数组中去,这个新数组的长度是扩容后的长度;注意,是新建了一个数组,并不是扩大原来数组的长度。

remove方法

删除一个元素,如下;实际上,源码用的方法也很笨拙。

public E remove(int index) {
        //不重要
        rangeCheck(index);
        //增加修改次数
        modCount++;
        //获取要被移除的数据
        E oldValue = (E) elementData[index];
        //计算出要移动的元素个数
        int numMoved = size - index - 1;
        //拷贝,其实就是用拷贝的方法把要删除的元素后面的元素往前移动一格
        //把当前数组的部分数据拷贝到当前数组的某个地方
        if (numMoved > 0)
            System.arraycopy(elementData, index + 1, elementData, index, numMoved);
        //由于上一步是拷贝,所以需要释放最后一格元素,方便垃圾回收
        elementData[--size] = null;
        return oldValue;
    }

modCount 

ArrayList内部会维护一个modCount整型变量,记录数组进行结构性修改的次数。add方法和remove方法都会使该变量加1。

迭代器

增强型for循环会转换成迭代器,或者说背后就是迭代器。所以,如果对象实现了Iterable接口,就可以使用增强型for循环。

在迭代过程中,无法添加、插入、删除元素,因为以上操作会改变数组结构,会导致迭代器维护的索引位置失效。但是可以使用迭代器的remove方法,因为remove方法会更新索引位置相关的数据。其实看看ArrayList的remove方法源码就知道了,该方法并没有针对索引位置数据进行任何操作。

关于迭代器,next()方法底层还是从数组中用get下标的方式获取数据,使用remove方法前要先使用next方法。

直接上源码,如下。

//为了更好理解代码,我们假设size为10
private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return 下一个元素的下标,初始为0
        int lastRet = -1; // index of last element returned; -1 if no such 最后一个返回的元素的下标
        int expectedModCount = modCount;//数组结构性修改的次数,modCount进行初始化。

        Itr() {}

        //判断是否存在下一个元素,size是外部类的对象属性,记录元素个数
        public boolean hasNext() {
            //代码逻辑很简单,cursor初始为0,调用next方法会让cursor加1,
            //只要没到达10,就说明还有下一个元素
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            //先检查数组是否发生了结构性变化,如果发生了结构性变化会抛出异常    
            checkForComodification();
            //这些不重要
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            //把cursor加1
            cursor = i + 1;
            //返回元素,并设置lastRet
            return (E) elementData[lastRet = i];
        }

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

            try {
                //调用外部remove方法删除元素
                this.remove(lastRet);
                /*注意,要先调用迭代器next方法才能调用本方法,所以这时候如果cursor是6,lastRet
                  应该是5。之所以要把cursor设为5,是因为调用了外部remove方法,之前6-9的元素移 
                  动到了5-8。*/
                cursor = lastRet;
                //由于元素被删除,所以上一个返回元素下标被设为不存在。
                lastRet = -1;
                //更新expectedModCount,否则调用迭代器其他方法时会抛出异常
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = java.util.ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = java.util.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();
        }
    }

 如果关于迭代器以上的内容看起来有些困惑,你只需记住以下内容:

迭代器内部维护了一个游标,这个游标指示当前所处位置。如果调用非迭代器内部的方法对数组进行增删,会改变数组结构,数组内的元素可能会移动,游标可能会失效。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值