集合框架-ArrayList学习和总结

一、ArrayList

贴上关键代码,有些方法做了合并,方便逻辑理解

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

    /**
     * 记录ArrayList的size被改变的次数,在父类AbstractList中定义
     * 为了提供快速失败机制:在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加,删除,修改),则会抛出 ConcurrentModification Exception
     * 典型的遍历一个集合删除某个元素时,应使用Iterator.remove(),避免使用List.remove(int index)
     */
    protected transient int modCount = 0;
    /**
     * size的大小和实际数组中的元素个数一致
     */
    private int size;
    /**
     * elementData.length是随着扩容变化的,和size不是一样的
     * 弄清楚minCapacity,size,elementData.length之间的关系就好了
     */
    transient Object[] elementData;
    /**
     * 有参构造器但传入的initialCapacity=0时(或addAll()传入的是一个空集合时),共享这个空数组,java8的一个改进
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 无参构造器初始化时共享这个空数组,保证初始化容量最后使用的是DEFAULT_CAPACITY=10
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 指定初始化容量的有参构造方法
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //指定容量为0时,用的是这个空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

    /**
     * jdk7中无参构造方法,初始化容量是10,对比来看像 饿汉  调用的是 this(10)
     * jdk8中,初始化容量是0,直到第一次调用add时才初始化为10,就像是懒汉,延迟了数组创建,节省内存
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 添加方法,modCount会增长
     * 先处理elementData的长度
     * 再给数组元素赋值
     */
    public boolean add(E e) {
        //希望的最小容量总是当前的size+1
        int minCapacity = size+1;
        //只有第一次从0开始的时候,这里才是true,第一次扩容后容量变为10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity =  Math.max(10, minCapacity);
        }
        modCount++;
        //overflow-conscious code,这个if判断是为了考虑int类型值溢出的情况,grow方法中也有
        int oldCapacity = elementData.length;
        if (minCapacity - oldCapacity > 0){
            //增长到1.5倍,右移后向下取整,依次是10,15,22
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //如果oldCapacity接近Integer.MAX_VALUE,newCapacity得到的结果可能为负数,下面的if就是负数结果
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            // 调用Arrays.copyOf,这个方法会调用System.arraycopy()
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
        elementData[size++] = e;
        return true;
    }

}

总结几个点:

1、建议创建ArrayList时指定初始容量

2、JDK8默认初始化的数组是空数组,容量不是10,第一次add时才第一次扩容;以后按照1.5倍(向下取整)扩容

3、ArrayList实现了RandomAccess接口,说明支持随机访问,建议优先使用一般的for循环遍历,而不要使用迭代器,注意,增强的foreach循环,内部使用的也是迭代器Iterator

subList方法

为了方便理解,原list叫sourceList,返回的叫subList

    /**
     * Returns a view of the portion of this list between the specified              ------返回一个当前ArrayList对象的左闭右开的视图
     * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.  (If
     * {@code fromIndex} and {@code toIndex} are equal, the returned list is
     * empty.)  The returned list is backed by this list, so non-structural          ------返回的list是基于当前父list的,非结构性的修改会彼此影响原
     * changes in the returned list are reflected in this list, and vice-versa.
     * The returned list supports all of the optional list operations.
     *
     * <p>This method eliminates the need for explicit range operations (of          ------- 应用场景:对原list的某一段元素进行操作时,可以通过subList视图进行操作来代替对整个list进行操作
     * the sort that commonly exist for arrays).  Any operation that expects
     * a list can be used as a range operation by passing a subList view
     * instead of a whole list.  For example, the following idiom
     * removes a range of elements from a list:
     * <pre>
     *      list.subList(from, to).clear();
     * </pre>
     * Similar idioms may be constructed for {@link #indexOf(Object)} and
     * {@link #lastIndexOf(Object)}, and all of the algorithms in the
     * {@link Collections} class can be applied to a subList.
     *
     * <p>The semantics of the list returned by this method become undefined if
     * the backing list (i.e., this list) is <i>structurally modified</i> in       -------结构性修改,也就是改变了sourceList的size的修改
     * any way other than via the returned list.  (Structural modifications are
     * those that change the size of this list, or otherwise perturb it in such
     * a fashion that iterations in progress may yield incorrect results.)
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     */
    public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

返回值是一个新的类,SubList,这是ArrayList的一个内部类,和ArrayList不是一个类型

    private class SubList extends AbstractList<E> implements RandomAccess {
        private final AbstractList<E> parent;
        private final int parentOffset;
        private final int offset;
        int size;
        //这个构造方法可以看出subList没有重新创建一个新的ArrayList
        SubList(AbstractList<E> parent,                       ------这个parent就是上面new SubList(this,0,fromIndex,toIndex)中的this,也就是sourceList
                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;
        }

        public E set(int index, E e) {
            rangeCheck(index);
            checkForComodification();
            E oldValue = ArrayList.this.elementData(offset + index);
            ArrayList.this.elementData[offset + index] = e;           ----------- 这个地方看出subList在set时,修改的是原ArrayList的elementData
            return oldValue;
        }

        public E get(int index) {
            rangeCheck(index);
            checkForComodification();
            return ArrayList.this.elementData(offset + index);         ------- 这个可以看出get时也是从原ArrayList的elementData获取的
        }
..........
}

总结:

1、将这个SubList强转成ArrayList会抛出 java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList 异常,同理也无法强转为其他的List

2、subList不会返回一个新的ArrayList,而是返回一个SubList,它是ArrayList里面的一个内部类,两者指向的同一套数据

3、对sourceList和subList做非结构性修改,影响是同步的

4、可以对subList做结构性修改,修改会反映到sourceList

5、对sourceList做结构性修改,对subList进行后续操作时,会抛出ConcurrentModificationException

贴一个阿里巴巴集合处理的代码规范说明

 

 6、正例

List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5,6));
list.subList(2,4).clear

//截取一部分,创建一个新的ArrayList
List<Integer> subList = new ArrayList<Integer>( sourceList.subList(0,3));

Collections.unmodifiableList

返回一个不可修改的List

public static <T> List<T> unmodifiableList(List<? extends T> list) {
   return (list instanceof RandomAccess ? new UnmodifiableRandomAccessList<>(list) : new UnmodifiableList<>(list));
}

Arrays.asList

    /**
     * Returns a fixed-size list backed by the specified array.  (Changes to
     * the returned list "write through" to the array.)  This method acts
     * as bridge between array-based and collection-based APIs, in
     * combination with {@link Collection#toArray}.  The returned list is
     * serializable and implements {@link RandomAccess}.
     *
     * <p>This method also provides a convenient way to create a fixed-size
     * list initialized to contain several elements:
     * <pre>
     *     List&lt;String&gt; stooges = Arrays.asList("Larry", "Moe", "Curly");
     * </pre>
     *
     * @param <T> the class of the objects in the array
     * @param a the array by which the list will be backed
     * @return a list view of the specified array
     */
    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

    /**
     * @serial include
     */
    private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }
.....
}

以上源码的说明:

1、Arrays.asList返回的是一个固定长度的list,返回类型不是java.util.ArrayList,而是Arrays的一个内部类ArraysList,这个类没有重写父类AbstractList的add,remove方法

2、如果调用上面的2个方法会抛出UnsupportedOperationException异常,因为实际会调用父类的这三个方法,如下:

    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
    public E remove(int index) {
        throw new UnsupportedOperationException();
    }

3、在使用上仅适用于对象类型的数组,不适用于基本类型的数组,比如Arrays.asList(new Integer[]{1,2,3}),size是3,而Arrays.asList(new int[]{1,2,3}),size是1,后者会当成一个整体

4、原数组和返回的list是相互关联的,改变list,原数组也会改变

String[] arr = new String[]{"a","b","c"};
List<String> list = Arrays.asList(arr);
list.set(0,"d");------->arr[0]会变为"d"

fail-fast机制

当遍历一个集合对象时,如果集合对象的结构被修改了,会抛出ConcurrentModficationExcetion异常,防止在遍历过程中出现意料之外的修改

源码注释:

/**
* <p><a name="fail-fast"> * The iterators returned by this class's {@link #iterator() iterator} and * {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a> * if the list is structurally modified at any time after the iterator is * created, in any way except through the iterator's own * {@link ListIterator#remove() remove} or * {@link ListIterator#add(Object) add} methods, the iterator will throw a * {@link ConcurrentModificationException}. Thus, in the face of * concurrent modification, the iterator fails quickly and cleanly, rather * than risking arbitrary, non-deterministic behavior at an undetermined * time in the future. * * <p>Note that the fail-fast behavior of an iterator cannot be guaranteed * as it is, generally speaking, impossible to make any hard guarantees in the * presence of unsynchronized concurrent modification. Fail-fast iterators * throw {@code ConcurrentModificationException} on a best-effort basis. * Therefore, it would be wrong to write a program that depended on this * exception for its correctness: <i>the fail-fast behavior of iterators * should be used only to detect bugs.</i>
*/

iterator()方法返回的new Itr()迭代器满足快速失败机制:

当创建一个迭代器后(调用iterator()或者增强的foreach循环),使用除迭代器的remove,add方法外的任何方法使得list被结构性改变

迭代器都会抛出一个ConcurrentModificationException,就是说宁可失败也不出错

下面的注意是说:快速失败机制是用来检验bug的,而不是让程序员依赖这个机制来设计程序逻辑

实现方式:

迭代器维护了一个expectedModCount,初始值等于ArrayList的modCount,当在迭代器遍历过程中单独调用list的add,remove时,会单方面修改modCount,迭代器

在调用next,remove等方法时都会调用checkModification()方法,改方法会比较两者是否相等

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

遍历过程中如何增加和删除元素

注意:并不是说遍历过程中不能对list进行结构性修改,而是说要让迭代器来干,而不是list自己直接动手改自己

正例:

Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
  Integer i = iterator.next();
  if (i<0) {
      iterator.remove();
  }
}

fail-safe机制

顺便提一嘴安全失败机制,是后面JUC工具类中的一种机制,与fail-fast相对应的

在安全的副本上进行遍历,当前集合修改和其副本在遍历时没有任何关系,但是缺点很明显,就是读取不到最新数据

这就是CAP理论中C(Consistency)和A(Availability)的矛盾,即一致性和可用性的矛盾。

上面的fail-fast发生时,程序会抛出异常,而fail-safe是一个概念,并发容器并发修改不会抛出异常,并发容器都是围绕着快照版本进行的操作,并没有modCount等数值检查

可以并发读取,不会抛出异常,但是不保证你的遍历读取的值和当前集合对象状态是一致的

所以fail-safe迭代缺点是:首先不能保证返回集合更新后的数据,因为是在集合的克隆上进行的操作,而非集合本身,其次创建集合拷贝需要相应的开销,包括时间和内存。

JUC包中集合的迭代,如ConcurrentHashMap、CopyOnWriteArrayList等默认的都是fail-safe

posted @ 2022-06-24 11:40  鼠标的博客  阅读(68)  评论(0编辑  收藏  举报