• 集合框架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等默认的都是faile-safe

  • 相关阅读:
    图解IntelliJ IDEA v13应用服务器的运行配置
    探秘IntelliJ IDEA v13的应用服务器
    WebStorm中Node.js项目配置教程(1)——创建项目
    Web神器WebStorm 8.0测试版发放(慧都独家)
    三个创建WebStorm项目的方法
    清明假期【未完成】
    DOM查询
    正则表达式
    Httpservlet cannot be resolved to a type的原因与解决方法
    解决ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)【亲测有效】
  • 原文地址:https://www.cnblogs.com/yb38156/p/16406071.html
Copyright © 2020-2023  润新知