欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

集合源码解析:ArrayList的底层实现原理

程序员文章站 2022-04-14 23:18:35
...

jdk7.0源码解析ArrayList底层实现原理


一、ArrayList的概述

ArrayList是List的实现类, 存储多个元素,有序、有下标、元素可以重复,允许null作为元素进行存储;线程不安全,因为底层方法为非同步。

二、ArrayList的存储结构

底层用数组实现,所以每个ArrayList实例都有一个容量,代表底层数组的长度,默认长度为10。随着存储的数据不断增加,底层数组需要扩容,扩容时需要将原数组中的数据拷贝到新数组中,所以如果能预计数量的多少,可在创建ArrayList对象时指定容量,如果一次性存储大量的元素时,可以调用ensureCapacity(int minCapacity)方法增加ArrayList的容量,从而减少扩容的次数。

集合源码解析:ArrayList的底层实现原理

三、ArrayList内部实现原理机制(源码解析)

  1. ArrayList的基本元素

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, 
                        java.io.Serializable
    {
        private static final long serialVersionUID = 8683452581122892189L;
    
    	// 默认的容量 为 10
        private static final int DEFAULT_CAPACITY = 10;
    	
    	// 用于共享的空实例数组对象
        private static final Object[] EMPTY_ELEMENTDATA = {};
    
        // 用于存储ArrayList元素的底层数组
        private transient Object[] elementData;
    	
    	// 记录 ArrayList中元素的个数,同时控制存储的下标
        private int size;
    
  2. ArrayList中的构造方法:

    /*
    	无参数的构造方法,采用默认的初始容量为10,
    	如果利用无参数的构造方法创建ArrayList对象,则
    	并没有完成 底层数组的初始化
    */
    public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }
    /*
    	用指定容量完成ArrayList对象的创建:
    	参数代表底层数组的长度,但是如果指定的长度小于0,则抛出异常,
    	如果基于此构造方法创建ArrayList对象,则创建ArrayList对象
    	时,完成底层数组的初始化
    */
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
    /*
    	将Collection集合作为参数构造 ArrayList 对象,
    	ArrayList集合用于 存储Collection集合中的数据
    */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, 
                                        Object[].class);
    }
    
  3. ArrayList中添加:add方法的实现

    /*
    	往ArrayList集合中添加一个元素,size统计底层数组中有效元素的个数,
    	同时控制存储的下标
    */
    public boolean add(E e) {
        // 在 内部确认容量是否够用
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 将 添加元素存储在底层数组相对应的位置
        elementData[size++] = e;
        return true;
    }
    
    // 确认容量
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
    
        ensureExplicitCapacity(minCapacity);
    }
    
    // 根据容量 判断 是否需要扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++; // 修改的次数加一
    	// 如果需要的最小容量,大于底层数组的长度,则扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) {
        // 获取底层数组的原始数组的长度
        int oldCapacity = elementData.length;
        /*
        	>> 代表右移,原数据除2取整数部分
        	新数组长度 = 原数组长度 + 原数组长度/2;
        	即:新数组长度是原数组长度的 1.5 倍左右
        */
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 数组的扩容 并 复制
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    

    解析:ArrayList底层数组在扩容时,每次扩容后的容量是原容量的 1.5 倍左右,扩容的

    ​ 同时需要将原始数组中元素一一拷贝到新数组中,通过 Arrays.copyOf()方法完成

    ​ 新数组的创建和元素的赋值,所以每一次的扩容,代价是很高的,所以我们在使

    ​ 用时,尽可能的避免扩容。如果在使用 ArrayList时,能预知存储元素的个数,可

    ​ 以在创建ArrayList 对象时,利用有参数的构造方法指定容量,或是根据实际容量

    ​ 的需求调用ensureCapacity(int minCapacity) 方法 指定容量。

  4. ArrayList中删除方法:remove

    // 根据下标删除对应元素
    public E remove(int index) {
        // 检测指定下标是否超过 最大下标
        rangeCheck(index);
    	
        //对集合的改变次数 + 1 
        modCount++;
        // 获取 底层数组对应的下标元素
        E oldValue = elementData(index);
    	// 需要移动元素的个数
        int numMoved = size - index - 1;
        // 被删除下标开始,后一个元素覆盖前一个元素
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 最后一个元素置 null,同时元素个数 - 1
        elementData[--size] = null; // clear to let GC do its work
    	// 将被删除的元素作为返回值返回
        return oldValue;
    }
    
    // 删除指定的元素
    public boolean remove(Object o) {
         // 如果 被删除的元素为null
         if (o == null) {
             // 遍历数组,删除第一个为null的元素
             for (int index = 0; index < size; index++)
                 if (elementData[index] == null) {
                     fastRemove(index);
                     return true;
                 }
         } else {
             /* 如果被删除的元素不为null,则遍历输出,删除第一个
                和指定数据相同的元素
             */
             for (int index = 0; index < size; index++)
                 if (o.equals(elementData[index])) {
                     fastRemove(index);
                     return true;
                 }
         }
         return false;
     }
    
  5. **ArrayList中添加:get方法的实现 **

    // 根据指定下标获取对应集合中的元素
    public E get(int index) {
        // 检测指定下标是否超过 最大下标
        rangeCheck(index);
    	// 调用 方法,获取底层数组对应的下标元素
        return elementData(index);
    }
    
  6. **ArrayList中添加:get方法的实现 **

// 修改指定下标对应的数组元素
public E set(int index, E element) {
    // 检测指定下标是否超过 最大下标
    rangeCheck(index);
    
	// 获取 底层数组对应的下标元素
    E oldValue = elementData(index);
    // 将新元素存储在 对应下标位置上
    elementData[index] = element;
    return oldValue;//被修改的数据作为返回值返回
}

四、Fail-Fast机制(快速失败)

  1. 在使用迭代器遍历集合过程中,对集合中对象的内容进行了修改(增、删、改)会抛出java.util.ConcurrentModificationException(并发修改异常),这就是所谓的Fail-Fast策略。

  2. 实现原理:在ArrayList的父类中定义了modCount属性,记录了对ArrayList的修改次数,只要对ArrayList中的内容进行修改,都会让modCount增加;在迭代器初始化时 ,会将modCount的值赋值给 expectedModCount ,每次迭代(nextNode操作)时,都会先判断
    modCount和expectedModCount 的值是否相等,如果不相等则代表ArrayList集合被操作,就会抛出java.util.ConcurrentModificationException异常。
    注意:modCount声明为volatile,保证线程之间修改的可见性。