集合判空的方法 java list集合判空工具类_ci


本系列第一篇文章今天正式开始,会按照本人的时间情况进行更新

阅读代码版本是JDK1.8,第一个包就先从util包开始吧

下图是ArrayList的UML图,由于是本系列的第一篇文章,所以我们把其中涉及到的接口全部的介绍一下,对数据结构有整体的认识



集合判空的方法 java list集合判空工具类_迭代器_02


1 Iterable:提供了两种迭代器:一种是顺序迭代器Iterator, 一种是可分割迭代器Spliterator,该可分割迭代器主要是通过降低每次迭代的宽度,增加并发,同时还提供相应的线程安全性;最后Iterable还提供了forEach方法;

2 Collection:提供了集合中绝大多数的基础操作方法,同时由于继承Iterable,所以需要实现两种迭代器;Stream与parallelStrream是1.8中的新特性,可以通过parallel()与sequential()方法在并行流和串行流之间进行切换。

3 AbstractCollection 抽象集合类

4 RandomAccess, Cloneable, Serializable为三个标记接口

标记接口的含义:

4.1 一般此标记接口用于 List 实现,以表明它们支持快速(通常是恒定时间)的随机访问。该接口的主要目的是允许通用算法改变其行为,以便在应用于随机或顺序访问列表时提供良好的性能。

  比如在工具类 Collections(这个工具类后面会详细讲解)中,应用二分查找方法时判断是否实现了 RandomAccess 接口:


int binarySearch(List<? extends Comparable<? super T>> list, T key) {
        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
            return Collections.indexedBinarySearch(list, key);
        else
            return Collections.iteratorBinarySearch(list, key);
    }


4.2 实现 Cloneable 接口

这个类是 java.lang.Cloneable,前面我们讲解深拷贝和浅拷贝的原理时,我们介绍了浅拷贝可以通过调用 Object.clone() 方法来实现,但是调用该方法的对象必须要实现 Cloneable 接口,否则会抛出 CloneNoSupportException异常。

  Cloneable 和 RandomAccess 接口一样也是一个标记接口,接口内无任何方法体和常量的声明,也就是说如果想克隆对象,必须要实现 Cloneable 接口,表明该类是可以被克隆的。

4.3 实现 Serializable 接口

这个没什么好说的,也是标记接口,表示能被序列化。

5

5.1 字段属性:


//集合的默认大小
        private static final int DEFAULT_CAPACITY = 10;
        //空的数组实例
        private static final Object[] EMPTY_ELEMENTDATA = {};
        //这也是一个空的数组实例,和EMPTY_ELEMENTDATA空数组相比是用于了解添加元素时数组膨胀多少
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
        //存储 ArrayList集合的元素,集合的长度即这个数组的长度
        //1、当 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时将会清空 ArrayList
        //2、当添加第一个元素时,elementData 长度会扩展为 DEFAULT_CAPACITY=10
        transient Object[] elementData;
        //表示集合的长度
        private int size;


5.2 构造函数

带容量参数构造函数,如容量为0,生成默认空数组对象


集合判空的方法 java list集合判空工具类_ci_03


默认构造函数,生成容量为10的AL


集合判空的方法 java list集合判空工具类_数组_04


将已有集合复制进去


集合判空的方法 java list集合判空工具类_数组_05


5.3 add函数


public boolean add(E e) { // 添加元素
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 判断元素数组是否为空数组
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 取较大值
        }
        
        ensureExplicitCapacity(minCapacity);
    }
    private void ensureExplicitCapacity(int minCapacity) {
        // 结构性修改加1 该值用于迭代器比较版本使用
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length; // 旧容量
        int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为旧容量的1.5倍
        if (newCapacity - minCapacity < 0) // 新容量小于参数指定容量,修改新容量
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0) // 新容量大于最大容量
            newCapacity = hugeCapacity(minCapacity); // 指定新容量
        // 拷贝扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }


总结添加元素:

1、当通过 ArrayList() 构造一个空集合,初始长度是为0的,第 1 次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置。

2、第 2 次添加元素,集合不为空,而且由于集合的长度size+1是小于数组的长度10,所以直接添加元素到数组的第二个位置,不用扩容。

3、第 11 次添加元素,此时 size+1 = 11,而数组长度是10,这时候创建一个长度为10+10*0.5 = 15 的数组(扩容1.5倍),然后将原数组元素引用拷贝到新数组。并将第 11 次添加的元素赋值到新数组下标为10的位置。

4、第 Integer.MAX_VALUE - 7 次添加元素时,创建一个大小为 Integer.MAX_VALUE 的数组,在进行元素添加。

5、第 Integer.MAX_VALUE + 1 次添加元素时,抛出 OutOfMemoryError 异常。

  注意:能向集合中添加 null 的,因为数组可以有 null 值存在。

5.4 删除元素

删除元素有两个方法,1根据索引,2直接删除指定元素,本质上这两个方法都是删除


public E remove(int index) {
        rangeCheck(index);//判断给定索引的范围,超过集合大小则抛出异常

        modCount++;
        E oldValue = elementData(index);//得到索引处的删除元素

        int numMoved = size - index - 1;
        if (numMoved > 0)//size-index-1 > 0 表示 0<= index < (size-1),即索引不是最后一个元素
            //通过 System.arraycopy()将数组elementData 的下标index+1之后长度为 numMoved的元素拷贝到从index开始的位置
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; //将数组最后一个元素置为 null,便于垃圾回收

        return oldValue;
    }


5.5 修改元素 与删除


public E set(int index, E element) {
        rangeCheck(index);//判断索引合法性

        E oldValue = elementData(index);//获得原数组指定索引的元素
        elementData[index] = element;//将指定所引处的元素替换为 element
        return oldValue;//返回原数组索引元素
    }


删除元素有两种方法,删除指定位置与删除指定元素,核心都为删除指定位置,删除原理将index后的元素整体前移一格,最后一个元素null,让gc进行整理


集合判空的方法 java list集合判空工具类_ci_06


5.6 访问元素


集合判空的方法 java list集合判空工具类_数组_07


其他批量删除,批量remove,整体清空,addAll等都可以进行组合使用;

5.7 遍历元素

遍历元素有两种方式1 根据index进行边界内的顺序访问 2根据迭代器的next进行顺序访问

AL由于继承Collection,所以需要实现Iterable,该实现在AL中为内部类


private class Itr implements Iterator<E> {
        int cursor;       //游标, 下一个要返回的元素的索引
        int lastRet = -1; // 返回最后一个元素的索引; 如果没有这样的话返回-1.
        int expectedModCount = modCount;

        //通过 cursor != size 判断是否还有下一个元素
        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];//返回索引为i处的元素,并将 lastRet赋值为i
        }

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

            try {
                ArrayList.this.remove(lastRet);//调用ArrayList的remove方法删除元素
                cursor = lastRet;//游标指向删除元素的位置,本来是lastRet+1的,这里删除一个元素,然后游标就不变了
                lastRet = -1;//lastRet恢复默认值-1
                expectedModCount = modCount;//expectedModCount值和modCount同步,因为进行add和remove操作,modCount会加1
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {//便于进行forEach循环
            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();
        }

        //前面在新增元素add() 和 删除元素 remove() 时,我们可以看到 modCount++。修改set() 是没有的
        //也就是说不能在迭代器进行元素迭代时进行增加和删除操作,否则抛出异常
       //解决办法是不调用 ArrayList.remove() 方法,转而调用 迭代器的 remove() 方法:
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }


注意:迭代器只能向后遍历,不能向前遍历,能够删除元素,但是不能新增元素。


集合判空的方法 java list集合判空工具类_迭代器_08


5.8 优化后迭代器


集合判空的方法 java list集合判空工具类_ci_09


Iterator迭代器只能向后遍历,而List可以向前遍历,也可以增加元素,实现方式是将游标进行向前移动,注意边界卡位

5.9 内部类 SubList

查看其数据结构发现,该类继承AbstractList,与AL本身行为几乎一致


集合判空的方法 java list集合判空工具类_ci_10


提供的操作方法也几乎一致


集合判空的方法 java list集合判空工具类_数组_11


由于子队列并没有生成新的AL,所以返回的仍然是父AL,所以对子AL的操作影响的是父AL


集合判空的方法 java list集合判空工具类_集合判空的方法 java_12


想完全生成一个独立子AL,只需要

List<String> subList = new ArrayList<>(list.subList(0, 1)); new一个出来

5.10 JDK1.8新引入的ArrayListSpliterator

典型的分治法思维


集合判空的方法 java list集合判空工具类_数组_13


其中核心方法为trySplit


集合判空的方法 java list集合判空工具类_数组_14