zoukankan      html  css  js  c++  java
  • Java基础11 Java集合之ArrayList源码解析

      从上篇Java集合的初步认识文章中,我们了解到 Collection 是Java集合的一大鼻祖,Collection 接口中定义了集合类的基本操作方法。在了解ArrayList之前,我们先来看看它的祖先Collection。

    Collection接口中的方法

    源码中 Collection 的定义

    public interface Collection<E> extends Iterable<E> {
    

    Collection 接口中的方法

    返回类型 方法 描述
    int size() 返回集合中的元素的个数
    boolean isEmpty() 若集合中不含有元素,返回true
    boolean contains(Object o) 若集合中含有某指定元素,返回true
    boolean containsAll(Collection<?> c) 若集合中包含指定集合中的所有元素,则返回true
    Iterator<E> iterator() 返回集合中元素的迭代器
    Object[] toArray() 把集合中元素放入数组中,返回数组
    <T> T[] toArray(T[] a) 返回包含此集合中所有元素的数组;返回数组的运行时类型是指定的数组的运行时类型
    boolean add(E e) 添加指定类型的元素到集合中
    boolean addAll(Collection<? extends E> c) 将指定集合中的所有元素添加到这个集合
    boolean remove(Object o) 移除集合中指定的元素
    boolean removeAll(Collection<?> c) 移除此集合包含指定集合的所有元素
    default boolean removeIf(Predicate<? super E> filter) 删除满足条件的元素(本接口已实现方法)
    boolean retainAll(Collection<?> c) 仅保留包含在指定集合中的这个集合中的元素
    void clear() 清空集合
    boolean equals(Object o) 将指定的对象与此集合比较
    int hashCode() 返回此集合的哈希值
    default Spliterator<E> spliterator() 创建此集合中的元素的 Spliterator(本接口已实现方法)
    default Stream<E> stream() 返回一个序列 Stream与集合的来源(本接口已实现方法)
    default Stream<E> parallelStream() 返回一个可能并行 Stream与集合的来源(本接口已实现方法)

    ArrayList

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

      从源码中ArrayList的定义,我们可以看出ArrayList是AbstractList的一个子类,同时又实现了 List、RandomAccess、Cloneable、Serializable 接口,使其具有快速随机访问可克隆可序列化的功能。

    ArrayList上层结构

    ArrayList
      通过上面的图,我们可以清晰的了解ArrayList的上层结构,ArrayList是List接口的一大实现类,也是List接口的最常用的实现类,下面就来详细的分析ArrayList。

    ArrayList的源码分析

    成员变量
    // 默认大小 10
    private static final int DEFAULT_CAPACITY = 10;
    // ArrayList 中实际元素的个数
    private int size;
    // ArrayList 存储元素的数组对象
    transient Object[] elementData;
    // 用于空实例的共享空数组实例(构造方法初始化对象是使用给定的大小)
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // 共享的空数组实例用于默认大小的空实例(构造方法初始化对象时使用默认大小)
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // ArrayList 默认最大的容量大小(设置Integer的最大值 - 8 是因为要空出8的大小给数组对象的 _length)
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    

      为什么初始化ArrayList对象时要分两个数组对象初始化呢?源代码是这样解释的 We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added是用来区分初始化ArrayList对象的方式是通过无参构造还是有参构造,这样做的目的是为了后面的扩容。

    构造方法
    // 无参构造方法,使用默认的空数组实例对象(大小为默认的大小 10)
    public ArrayList() {
        this.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);
        }
    }
    // 有参构造方法,为空的时候也是使用空数组实例对象;不为空时直接拷贝传入的对象元素放入到ArrayList中,大小为传入对象的大小
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    

      上面的三种构造方法对应着三种的初始化场景,无参构造方法主要是用来初始化一个为空的ArrayList对象,底层使用的是静态的 DEFAULTCAPACITY_EMPTY_ELEMENTDATA Object对象实例数组;而另外两个有参构造方法目的是用来初始化用户想要大小的ArrayList对象,当用户给定的大小为 0 的时候,底层使用静态的 EMPTY_ELEMENTDATA Object对象实例数组;当这两种有参方法传入的参数大小不为 0 的时候,底层使用的是给定大小的动态的 Object 数组对象。这样做的目的是为了后面的扩容策略。

    主要的方法源码解析
    add(E e) 添加单个元素
    // add 方法的主入口
    public boolean add(E e) {
    	// 主要解决AraayList大小问题的方法主入口
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 把传入的元素添加到ArrayList中
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    // 判断传入的大小是否大于ArrayList的大小
    private void ensureExplicitCapacity(int minCapacity) {
    	// 每次操作都计数一次,该字段来源于ArrayList的父类AbstractList抽象类,主要作用是为了避免遍历的时候修改ArrayList对象
        modCount++;
    
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
        	// ArrayList 的扩容的方法
            grow(minCapacity);
    }
    // 判断ArrayList的大小,若是无参方法创建的实例就取默认大小与传入大小的最大值,否则直接取传入值
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    

      经过对ArrayList源代码的分析,我们可以知道每次添加元素之前都会确认ArrayList容量的大小,再去size++。在确认ArrayList容量大小的过程中,我们可以看到源代码是先去对容量大小参数minCapacity做一个粗略的判断,在方法 calculateCapacity中我们可以看到是做了分底层数组对象处理,若为DEFAULTCAPACITY_EMPTY_ELEMENTDATA(也就是无参构造函数实例化出来的默认的静态数组对象)对象,则取默认大小(10)与传入大小的最大值,若是有参构造实例化出来的对象,则直接取传入的minCapacity值;然后在ensureExplicitCapacity方法中去做是否进行扩容操作的判断。扩容的实际操作是在 grow 方法中,我们接下来看看ArrayList是怎样扩容的。

    grow(int minCapacity) 扩容操作
    private void grow(int minCapacity) {
        // overflow-conscious code
        // 记录原ArrayList的容量大小
        int oldCapacity = elementData.length;
        // 使用为位运算的方式进行扩容,扩为原来大小的 1.5 倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 判断扩容后的大小与传入的大小比较,若还小,就直接让新容量大小为传入的大小
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 若扩容后的大小超过 Integer.MAX_VALUE - 8 的大小,则使用 Integer.MAX_VALUE
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        // 将数据拷贝到扩容后的 ArrayList 中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    // 取 Integer.MAX_VALUE 值做ArrayList的容量大小
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    

      对ArrayList的扩容方法的源码分析,我们可以了解到ArrayList的默认一次扩容大小为 1.5 倍原来的容量大小;源码对扩容后的大小又做了进一步的判断,若扩容后的大小还不满足用户的需求,则直接把ArrayList的容量大小设为用户需要的大小(传入的大小),最后对最终的容量大小又进一步判断,判断是否超过 Integer.MAX_VALUE - 8 = 2147483639 的大小,超过则直接给 Integer.MAX_VALUE = 2147483647 的大小,若还超过,那么对不起,直接报错O(∩_∩)O哈哈~。
      为什么ArrayList的最大容量大小设置为 Integer.MAX_VALUE - 8 ?关于这个问题,细心地看官应该能注意到,我在列出ArrayList成员变量的时候简单的解释了这个问题;其实也不难理解,数组是一个对象,内存在存储对象的时候,不但要存储对象中的元素,还要存储对象的头信息,对象的头信息不可能会超过 8 ,所以ArrayList的默认最大的容量就设置为 Integer.MAX_VALUE - 8;但后面又为什么把Integer的最大值给ArrayList的容量呢?这里其实是想最大限度的提高内存的使用率,对象的头信息不可能超过 8 ,也就是说对象的头信息有可能只需要占 4 的大小就可以了,有进一步占据空间的可能性。

    add(int index, E element) 指定位置上插入一个元素
    public void add(int index, E element) {
    	// 对传入的下标进行有效检测判断
        rangeCheckForAdd(index);
    	// 尝试改变 ArrayList 容量的大小
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 调用底层的数组拷贝方法,空出下标为 index 的位置
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // 把元素添加到指定下标的位置
        elementData[index] = element;
        // ArrayList 的实际元素的大小 +1
        size++;
    }
    
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    addAll(Collection<? extends E> c) 添加一个集合到ArrayList中
    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;
    }
    
    addAll(int index, Collection<? extends E> c) 从ArrayList指定下标开始添加一个集合元素
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);
    
        Object[] a = c.toArray();
        int numNew = a.length;
        // 尝试改变 ArrayList 容量的大小
        ensureCapacityInternal(size + numNew);  // Increments modCount
    
        int numMoved = size - index;
        if (numMoved > 0)
        	// 腾出空间来存储传入的集合元素
            System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
    	// 将传入的集合元素添加到 ArrayList 指定位置
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
    

    分析完ArrayList的添加操作,我们解下来分析下ArrayList的删除操作。

    remove(int index) 移除指定下标的元素
    public E remove(int index) {
    	// 检测给定的下标是否在规定的范围内
        rangeCheck(index);
    	// 修改ArrayList操作的计数器
        modCount++;
        // 取出指定下标的元素,用于返回
        E oldValue = elementData(index);
    
        int numMoved = size - index - 1;
        // 指定下标不是ArrayList的最后一个元素的下标时,调用底层数组的拷贝方法
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
        // 将原先ArrayList的最后以一个元素设置为null,便于被回收,保证不浪费内存空间
        elementData[--size] = null; // clear to let GC do its work
    
        return oldValue;
    }
    
    private void rangeCheck(int index) {
    	// 只做了向上的越界,没有做向下的越界的原因在于给定下标index < 0,ArrayList在调用底层数组拷贝时,不会做元素的移动
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    E elementData(int index) {
        return (E) elementData[index];
    }
    
    removeRange(int fromIndex, int toIndex) 移除一个范围的元素
    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved);
    
        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }
    
    remove(Object o) 移除指定的元素
    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;
    }
    
    fastRemove(int index) 快速移除
    // 快速移除方法,所谓开始就是不做任何判断,只专注做移除元素的业务操作,其限制判断在别的方法做了限制,保证了其传入的 index 的有效性
    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
    }
    
    removeAll(Collection<?> c) 移除与指定集合元素相匹配的元素
    public boolean removeAll(Collection<?> c) {
    	// 判断传入的集合是否为空,调用 Object 类的不为空的方法
        Objects.requireNonNull(c);
        // 批量移除
        return batchRemove(c, false);
    }
    
    private boolean batchRemove(Collection<?> c, boolean complement) {
    	// 定义不可变的变量指向外部的 elementData 的内存地址
        final Object[] elementData = this.elementData;
        // r 表示读. w表示写
        int r = 0, w = 0;
        boolean modified = false;
        try {
        	// for 循环保留与传入的集合c中的元素不匹配的数据,存储在elementData(方法里的变量) 
            for (; r < size; r++)
            	// complement 是一个标记, true表示保留, false表示移除
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            // 抛出异常的情况下,移除抛异常之前的与传入参数的元素相匹配的数据
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            // 没有抛异常,正常遍历结束,移除 w 后面的所有元素(置null)
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }
    
    retainAll(Collection<?> c) 保留与指定集合元素相匹配的元素
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        // true,保留与之相匹配的元素,方法在上面代码框内
        return batchRemove(c, true);
    }
    

       通过分析 removeAll 的源代码,我们ArrayList的方法设计非常解耦、通用,它通过一个complement标记,省略了极大的重复代码,这个标记被 removeAllretainAll 两个方法所使用,前者是移除与传参集合中元素相匹配的数据,后者是保留与传参集合中元素相匹配的数据。

    clear() 清空所有
    public void clear() {
       modCount++;
    
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
    
        size = 0;
    }
    
    removeIf(Predicate<? super E> filter) 移除满足条件的元素
    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        // figure out which elements are to be removed
        // any exception thrown from the filter predicate at this stage
        // will leave the collection unmodified
        int removeCount = 0;
        // 使用BitSet对象记录要移除元素的下标
        final BitSet removeSet = new BitSet(size);
        // 用于判别对ArrayList的改动次数是否一致,主要就是防止多线程修改
        final int expectedModCount = modCount;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            @SuppressWarnings("unchecked")
            final E element = (E) elementData[i];
            // 若集合元素满足指定的规则,则将BitSet中对应下标位置的值设为true
            if (filter.test(element)) {
                removeSet.set(i);
                removeCount++;
            }
        }
        // 判断其他线程是否对该对象集合做了改动
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    
        // shift surviving elements left over the spaces left by removed elements
        final boolean anyToRemove = removeCount > 0;
        if (anyToRemove) {
        	// newSize 为过滤之后集合的实际大小
            final int newSize = size - removeCount;
            for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
            	// nextClearBit 返回设置为false的第一个位置的索引(下标)
                i = removeSet.nextClearBit(i);
                // 保存不符合断言条件的元素,变相地移除了满足条件的元素
                elementData[j] = elementData[i];
            }
            // 将newSize后面的元素全部置null
            for (int k=newSize; k < size; k++) {
                elementData[k] = null;  // Let gc do its work
            }
            this.size = newSize;
            // 判断其他线程是否对该对象集合做了改动
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            modCount++;
        }
    
        return anyToRemove;
    }
    

       removeIf方法是对Collection接口方法的重写,源码也说了在执行该方法的过程中,抛出任何异常都会中断操作,不会对ArrayList做任何变更性操作,方法是根据过滤条件去移除满足条件的数据,里面运用到 BitSet 这个在后续讲解Set系列的时候回去详细的讲解,这里只针对运用到的方法做简单地讲解。removeIf 方法的核心就在于对BitSet的运用,分别用到了set() 和 nextClearBit() 方法,set()方法是将满足过滤条件的下标设置为true,nextCLearBit()方法是取出为false的下标。
    举个例子:

    ArrayList集合元素下标情况:[0, 1, 2, 3, 4, 5, 6]
    满足断言过滤条件的下标情况:[3, 5, 6]
    for循环调用nextCLearBit()得出不满足过滤条件的下标:[0, 1, 2, 4]
    

      通过分析ArrayList的移除操作的源代码,我们可以知道ArrayList的移除操作其实是很简单的,就是普通的数组移除元素的思想,先对传入的参数做有效性判定,然后就是找到要删除元素的下标,再调用底层的数组相应拷贝方法,移动ArrayList数组,最后就做相应的返回。

    replaceAll(UnaryOperator operator) 替换满足条件的元素

    从方法参数可以看出参数是一个函数式接口,这就意味着参数可以使用Lambda表达式

    @Override
    @SuppressWarnings("unchecked")
    public void replaceAll(UnaryOperator<E> operator) {
    	// 不为空校验
        Objects.requireNonNull(operator);
        // List结构被修改的次数
        final int expectedModCount = modCount;
        final int size = this.size;
        // 没有被多线程修改结构
        for (int i=0; modCount == expectedModCount && i < size; i++) {		
        	// 按照 lamdba 表达式做相应的操作
            elementData[i] = operator.apply((E) elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }
    
    // UnaryOperator 接口
    @FunctionalInterface
    public interface UnaryOperator<T> extends Function<T, T> {
    
        /**
         * Returns a unary operator that always returns its input argument.
         *
         * @param <T> the type of the input and output of the operator
         * @return a unary operator that always returns its input argument
         */
        static <T> UnaryOperator<T> identity() {
            return t -> t;
        }
    }
    // List 里面的方法
    default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }
    

    示例:

    @Test
    public void replaceTest(){
        List<Integer> list = createData();  // 生成 0 - 9 的数字List
        list.iterator().forEachRemaining(i -> System.out.print(String.valueOf(i) + "、"));
        // 把元素为 3 的改为 13
        list.replaceAll(i -> i == 3 ? 13 : i);
        System.out.println();
        list.iterator().forEachRemaining(i -> System.out.print(String.valueOf(i) + "、"));
    }
    

    输出显示:

    0、1、2、3、4、5、6、7、8、9、
    0、1、2、13、4、5、6、7、8、9、
    
    sort(Comparator<? super E> c) 按条件排序

    该方法的参数也是一个函数式接口,条件就可以通过这个接口去设定

    @Override
    @SuppressWarnings("unchecked")
    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }
    

    示例:

    @Test
    public void sortTest(){
        List<Integer> list = createData();
        list.iterator().forEachRemaining(i -> System.out.print(String.valueOf(i) + "、"));
        System.out.println();
        list.sort((a, b) -> b.compareTo(a)); // 置为倒序
        list.iterator().forEachRemaining(i -> System.out.print(String.valueOf(i) + "、"));
    }
    

    输出结果:

    0、1、2、3、4、5、6、7、8、9、
    9、8、7、6、5、4、3、2、1、0、
    
    iterator 迭代器
    iterator() 用于迭代输出
    public Iterator<E> iterator() {
        return new Itr();
    }
    

      从源码中,可以看出,调用 iterator 方法,其实就是new 出来一个内部类对象,我们接下来就来看看此 Itr 内部类。

    ArrayList 的内部类

    Itr 内部类
    private class Itr implements Iterator<E> {
    
    成员变量
    // 用于指向下一个要返回的值
    int cursor;       // index of next element to return
    // 用于返回值的下标(在实际操作中,会返回下标[lastRet + 1]的值)
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;
    
    构造方法
    Itr() {}
    
    hasNext() 判断后面是否还存在元素
    public boolean hasNext() {
        return cursor != size;
    }
    
    next() 取出元素
    public E next() {
    	// 检测是否有其他线程对此对象进行操作
        checkForComodification();
        int i = cursor;
        // 判断cursor后面是否还有元素
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        // 进一步保证不会越界
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        // 返回curson移动之前下标对应的元素,同时把lastRet指向curson移动之前下标
        return (E) elementData[lastRet = i];
    }
    
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
    

       这里解释的可能不太好,大家意会下,就是两个指针,一前一后的指向相邻的两个元素。

    remove() 移除元素
    public void remove() {
    	// 对 lastRet 做判断,防止没有经过 next 就开始删除元素
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();
    
        try {
        	// 调用外部类的 remove 方法,直接把指定下标(lastRet)后面的元素网前移动
            ArrayList.this.remove(lastRet);
            // 将cursor指向lastRet,因为lastRet后面所有的元素都往前移动一位,cursor的值也就必须往前移
            cursor = lastRet;
            // 将lastRet的值重新设置为起始值,防止在遍历的过程恶意进行无限循环删除
            lastRet = -1;
            // 将期望修改值重新赋值为修改值,目的就是为了能够进行多次操作,因为经过一次删除操作,modCount加了1,在next()方法开始之前,就对这两个进行了一次相等校验,不重新赋值,下一次调用next就会报错,这将会和普通的for一样,在遍历的过程中不能对ArrayList进行修改性操作
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
    

      通过对内部类Itr的 hasNextnextremove 三个方法的源代码的解读,我们终于知道为什么iterator迭代器支持遍历的同时对ArrayList对象进行修改性的操作,原因就在于内部类 remove 方法的 expectedModCount = modCount 语句,它可以使得删除操作对修改次数的校验不会抛异常。

    forEachRemaining(Consumer<? super E> consumer) 迭代器遍历
    @Override
    @SuppressWarnings("unchecked")
    // 该方法重写了 Iterator 接口中的方法,思维逻辑很简单,我这就不过多的解释
    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();
    }
    

      该方法支持流操作,可以对元素做用户自己想做的操作,很灵活,建议大家能多使用这个方法。

    ListItr 内部类

    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }
    
    private class ListItr extends Itr implements ListIterator<E> {
    

      该内部类继承了 Itr 内部类,所以它具备 Itr 所有操作;但由于 Itr 类只能做遍历中删除,不能做遍历中修改和插入,为了弥补这一缺陷,就出现了 ListItr 内部类。

    构造方法
    ListItr(int index) {
        super();
        cursor = index;
    }
    
    三个基础方法
    // 判断当前元素前面是否有元素
    public boolean hasPrevious() {
        return cursor != 0;
    }
    // 获取当前的下标
    public int nextIndex() {
        return cursor;
    }
    // 获取前一个数的下标
    public int previousIndex() {
        return cursor - 1;
    }
    
    操作方法
    previous() 获取前一个元素
    public E previous() {
    	// 检验期望变更次数与变更次数是否相等,防止多线程操作
        checkForComodification();
        // 前元素下标
        int i = cursor - 1;
        if (i < 0)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i;
        // 返回前一个元素的值
        return (E) elementData[lastRet = i];
    }
    
    set(E e) 将指定下标的值设置为参数e
    public void set(E e) {
    	// 这个判断就限制了该方法必须经过一次next或previous方法且lastRet 大于等于0
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();
    
        try {
            ArrayList.this.set(lastRet, e);
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
    
    add(E e) 添加一个元素
    public void add(E e) {
        checkForComodification();
    	// 思想与remove是一样的,这里只不过是添加,所以应该cursor往后移动
        try {
            int i = cursor;
            ArrayList.this.add(i, e);
            cursor = i + 1;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
    

    SubList 内部类

    生成方法:

    public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }
    
    static void subListRangeCheck(int fromIndex, int toIndex, int size) {
       if (fromIndex < 0)
           throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
       if (toIndex > size)
           throw new IndexOutOfBoundsException("toIndex = " + toIndex);
       if (fromIndex > toIndex)
           throw new IllegalArgumentException("fromIndex(" + fromIndex +
                                              ") > toIndex(" + toIndex + ")");
    }
    
    private class SubList extends AbstractList<E> implements RandomAccess {
    
    成员变量
    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;
    
    构造函数
    SubList(AbstractList<E> parent, int offset, int fromIndex, int toIndex) {
    	// 这里指的父类不是继承的那种关系,是指外部类,说父类只是便于说明
        this.parent = parent;	// 指向父类
        this.parentOffset = fromIndex;	// 父类的偏移量
        this.offset = offset + fromIndex;	// 子类的偏移量
        this.size = toIndex - fromIndex;	// 子类的实际大小
        this.modCount = ArrayList.this.modCount;	// 修改次数
    }
    

      观察这个构造方法,这个子List不是单独生成的,它是指向父List的,所以它内部的方法操作的结果必定会影响到父List的元素,反之亦然。

    方法

      SubList的内部类中的方法与外部类ArrayList的方法的实现思想是一样的,只不过是添加了一些偏移量而已;由于有太多重复的,前面分析ArrayList的的时候大部分都已经说过,只要能理解前面的,这里的也自然能理解,我就一一在解读一遍,只挑几个方法来解读。
      重要:SubList内部类要慎用,尽量不要对SubList做修改操作。

    add(int index, E e) 在指定位置添加一个元素
    public void add(int index, E e) {
        rangeCheckForAdd(index);
        checkForComodification();
        // 直接对父List的指定位置添加一个元素
        parent.add(parentOffset + index, e);
        this.modCount = parent.modCount;
        this.size++;
    }
    
    remove(int index) 移除指定位置的元素
    public E remove(int index) {
        rangeCheck(index);
        checkForComodification();
        // 直接移除父List指定位置的一个元素
        E result = parent.remove(parentOffset + index);
        this.modCount = parent.modCount;
        this.size--;
        return result;
    }
    

      从添加和删除中,我们可以看到所有修改操作,都会直接去操作父List,所以会直接影响到父List的数据结构。SubList内部类里面也有 iterator 方法,该方法是创建一个 ListIterator 内部类(SubList里面的内部类),器实现思想都是一样的,我这里就不再去解析了,有兴趣的可以自己去看源码,很好理解的,一看就能懂得。

    iterator()
    public Iterator<E> iterator() {
        return listIterator();
    }
    
    public ListIterator<E> listIterator(final int index) {
        checkForComodification();
        rangeCheckForAdd(index);
        final int offset = this.offset;
    
        return new ListIterator<E>() { ... }
    

    总结

    1. ArrayList底层结结构为 Object 数组,其默认容量为 10,默认扩容大小为 1.5 倍,一次扩容不成功则 newCapacity > MAX_ARRAY_SIZE ? Integer.MAX_VALUE : MAX_ARRAY_SIZE
    2. ArrayList 有三种初始化对象,分别是无参构造产生的静态Object数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,有参构造参数是空数据产生的静态Object数组 EMPTY_ELEMENTDATA ,有参构造参数是有数据产生的动态 Object 数组。
    3. ArrayList 在for循环中不能多次修改操作,可以在Iterator里面做多次修改操作。
    4. ArrayList 有四大 IteratorListIteratorSubListArrayListSpliterator 内部类。Iterator 只支持向后遍历,ListIterator支持双向遍历, SubList 要慎用,ArrayListSpliterator 把List均匀分割成几部分。
    5. ArrayList本质是数组,所以它的查找速率很快,而插入删除,性能会比较低。
  • 相关阅读:
    C# Winform 国际化
    Could not find method google() for arguments [] on repository container,rn 集成react-native-camera 出现此错误的解决方法
    RN集成echarts4图表组件react-native-secharts(转载)
    DataGridView设置行高
    react native原生模块引用本地jar包
    react-native-printer
    phpstudy连接SQL Server 2008数据库 以及 php使用sql server出现乱码解决方式
    Apache崩掉:为进程配置合适的线程数
    引用静态资源时加上时间戳,处理浏览器缓存问题
    利用存储过程优化复杂的数据库操作
  • 原文地址:https://www.cnblogs.com/sophia-show/p/13684288.html
Copyright © 2011-2022 走看看