ArrayList方法源码 整理

·  阅读 1131
ArrayList
讲解

ArrayList 添加的元素,是有序,可重复,有索引的

图片.png

public static void main(String[] args){
    List<String> lists = new ArrayList<>();//多态
    lists.add("java1");
    lists.add("java1");//可以重复
    lists.add("java2");
    for(int i = 0 ; i < lists.size() ; i++ ) {
            String ele = lists.get(i);
            System.out.println(ele);
   }
}
复制代码

源码

ArrayList 实现类集合底层基于数组存储数据的,查询快,增删慢,支持快速随机访问

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}
复制代码
  • RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
  • ArrayList 实现了 Cloneable 接口 ,即覆盖了函数 clone(),能被克隆
  • ArrayList 实现了 Serializable 接口,这意味着 ArrayList 支持序列化,能通过序列化去传输

核心方法:

  • 构造函数:以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量(惰性初始化),即向数组中添加第一个元素时,数组容量扩为 10

  • 添加元素:

    // e 插入的元素  elementData底层数组   size 插入的位置
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);	// Increments modCount!!
        elementData[size++] = e;			// 插入size位置,然后加一
        return true;
    }
    复制代码

    当 add 第 1 个元素到 ArrayList,size 是 0,进入 ensureCapacityInternal 方法,

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    复制代码
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // 判断elementData是不是空数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // 返回默认值和最小需求容量最大的一个
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    复制代码

    如果需要的容量大于数组长度,进行扩容:

    // 判断是否需要扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // 索引越界
        if (minCapacity - elementData.length > 0)
            // 调用grow方法进行扩容,调用此方法代表已经开始扩容了
            grow(minCapacity);
    }
    复制代码

    指定索引插入,在旧数组上操作

    public void add(int index, E element) {
        rangeCheckForAdd(index);
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 将指定索引后的数据后移
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        elementData[index] = element;
        size++;
    }
    复制代码
  • 扩容:新容量的大小为 oldCapacity + (oldCapacity >> 1)oldCapacity >> 1 需要取整,所以新容量大约是旧容量的 1.5 倍左右,即 oldCapacity+oldCapacity/2

    扩容操作需要调用 Arrays.copyOf()(底层 System.arraycopy())把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数

    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //检查新容量是否大于最小需要容量,若小于最小需要容量,就把最小需要容量当作数组的新容量
        if (newCapacity - minCapacity < 0)
    		newCapacity = minCapacity;//不需要扩容计算
        //检查新容量是否大于最大数组容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`
            //否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    复制代码

    MAX_ARRAY_SIZE:要分配的数组的最大大小,分配更大的可能会导致

    • OutOfMemoryError:Requested array size exceeds VM limit(请求的数组大小超出 VM 限制)
    • OutOfMemoryError: Java heap space(堆区内存不足,可以通过设置 JVM 参数 -Xmx 来调节)
  • 删除元素:需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,在旧数组上操作,该操作的时间复杂度为 O(N),可以看到 ArrayList 删除元素的代价是非常高的

    public E remove(int index) {
        rangeCheck(index);
        modCount++;
        E oldValue = elementData(index);
    
        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
    
        return oldValue;
    }
    复制代码
  • 序列化:ArrayList 基于数组并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,就没必要全部进行序列化。保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化

     transient Object[] elementData;
    复制代码
  • ensureCapacity:增加此实例的容量,以确保它至少可以容纳最小容量参数指定的元素数,减少增量重新分配的次数

     public void ensureCapacity(int minCapacity) {
         if (minCapacity > elementData.length
             && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
                  && minCapacity <= DEFAULT_CAPACITY)) {
             modCount++;
             grow(minCapacity);
         }
     }
    复制代码
  • Fail-Fast:快速失败,modCount 用来记录 ArrayList 结构发生变化的次数,结构发生变化是指添加或者删除至少一个元素的操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化

    在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,改变了抛出 ConcurrentModificationException 异常

    public Iterator<E> iterator() {
        return new Itr();
    }
    复制代码
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;
    
        Itr() {}
    
        public boolean hasNext() {
            return cursor != size;
        }
    
       	// 获取下一个元素时首先判断结构是否发生变化
        public E next() {
            checkForComodification();
           	// .....
        }
        // modCount 被其他线程改变抛出并发修改异常
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    	// 【允许删除操作】
        public void remove() {
            // ...
            checkForComodification();
            // ...
            // 删除后重置 expectedModCount
            expectedModCount = modCount;
        }
    }
    复制代码

    本文正在参加「金石计划 . 瓜分6万现金大奖」

收藏成功!
已添加到「」, 点击更改