美文网首页
ArrayList的源码解读

ArrayList的源码解读

作者: 明月清风被占用 | 来源:发表于2020-03-27 23:18 被阅读0次
    ArrayList 的应用场景

    ArrayList的底层数据结构是数组,也就是说它满足数组的特点,数组的优势在于:它在物理内存上的地址是一块连续的内存空间,这无疑对查询的操作是友好的,查询的时间复杂度为O(1),但就结构改变的操作是很浪费时间的,因为当数组的元素很多的时候,ArrayList集合下大部分删除增加方法的时间复杂度接近O(n),add(E e)除外,它是直接插入在数组末尾,时间复杂度为O(1)。

    它与List接口下的其他集合的区别

    List接口下有三个集合类:ArrayList、LinkedList、Vector

    • LinkedList:底层是基于双向链表实现的,查询慢、增删效率高。
        transient Node<E> first;
    
        /**
         * Pointer to last node.
         * Invariant: (first == null && last == null) ||
         *            (last.next == null && last.item != null)
         */
        transient Node<E> last;
    
    • Vector:底层是基于数组实现,同ArrayList相同,只不过它是线程安全的集合,被synchronize修饰
        public synchronized void copyInto(Object[] anArray) {
            System.arraycopy(elementData, 0, anArray, 0, elementCount);
        }
    

    当然List接口下面还有很多子类的实现类:比如Stack,它扩展自Vector集合,这里不会展开它,后期它处再详细解释。

    ArrayList集合中的一些方法
    • add(E e),增加元素,如下是这个代码的执行流程,难懂的地方加了注释,请12345按照序列阅读
    transient Object[] elementData; 
    
    //当你未指定容量大小的时候 如:List list = newArrayLIst();
    //它的底层数组容量默认采用这个常量
    private static final int DEFAULT_CAPACITY = 10;  
    
    public boolean add(E e) {
           //1、判断当前数组容量的大小是否能执行增加元素这一操作
            ensureCapacityInternal(size + 1);
           //5、将这个新元素放在index++这个索引位置
            elementData[size++] = e;
            return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
            //2、如果数组的长度确实和默认大小是相等的
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //3、取这俩个之中大的那一个给这个minCapacity变量
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
             //4 确定新数组的大小
            ensureExplicitCapacity(minCapacity);
    }
    private void ensureExplicitCapacity(int minCapacity) {
            //4.1  protected transient int modCount = 0; 
            //这个值是用来判断并发操作下
            //处于迭代器遍历下的这个集合里面的值有没有被其他线程修改,如果变了,那么直接抛出异常
            //这个机制称之为快速失败机制
            modCount++;
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                //4.2
                grow(minCapacity);
    }
    
    /*
    这个就是它的扩容机制了
    */
    private void grow(int minCapacity) {
            // 原数组容量大小,是指这个数组的长度而不是元素个数
            int oldCapacity = elementData.length;
            // 新数组的容量是原来的1.5倍。这里>>1操作是进行了一个移位运算,向右移一位,相当于除2
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //这下面就是和原来的miniCapacity比较、然后取值
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            //这里值的注意的是,当你的数组容量起初已经很大的时候,可能会超出它对数组本来的大小限制
            //这时候就会进行一个再取值,反正最后的数组大小就是它指定的一个大小。不是你的值
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // 这里就是将原数组的内容拷贝到新长度的数组容器中,并覆盖掉原数组,让GC回收它
            elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    • add(int index, E element),按指定索引添加
    public void add(int index, E element) {
            //检查索引范围是否合法
            rangeCheckForAdd(index);
            //熟悉的方法,同add里的相同,这一步是再判断集合里面还有没有容量来容下这个元素
            ensureCapacityInternal(size + 1); 
            //首先我们要明确一点,这个集合底层的删除和按索引添加其实就是创建了一个新的数组,将旧数组里的
            //部分信息复制进新数组,下面这个方法可以这么理解
            //参数:源数组、从源数组中的起始位置、目标数组、从目标数组的起始位置、复制的步长
            System.arraycopy(elementData, index, elementData, index + 1,size - index);
            //将元素放入新数组
            elementData[index] = element;
            size++;
    }
    
    • remove(int index) 删除元素
    protected transient int modCount = 0;
    public E remove(int index) {
            //索引检查是否合法
            rangeCheck(index);
            //基于快速失败机制,为保证集合的安全性,如果这个modCount被修改,直接抛出异常,终止操作
            modCount++;
            E oldValue = elementData(index);
            //需要移动的元素个数
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,numMoved);
            //末尾索引位置的元素设置未null
            elementData[--size] = null; // clear to let GC do its work
            return oldValue;
    }
    
    • removeRange(int fromIndex, int toIndex) 删除指定范围内的元素
    private final AbstractList<E> l;
    private final int offset;
    private int size;
    
    protected void removeRange(int fromIndex, int toIndex) {
            modCount++;
            int numMoved = size - toIndex;
            //复制数组,从源数组toIndex位置开始,目标数组fromIndex位置开始。复制源数组numCount个元素
            System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);
    
            // 将末尾空出来的位置numMoved个位置设置为null,等待GC回收
            int newSize = size - (toIndex-fromIndex);
            for (int i = newSize; i < size; i++) {
                elementData[i] = null;
            }
            size = newSize;
    }
    

    相关文章

      网友评论

          本文标题:ArrayList的源码解读

          本文链接:https://www.haomeiwen.com/subject/snxluhtx.html