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

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


一、ArrayList的概述

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

二、ArrayList的存储结构

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C1z7S1B1-1586450232815)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1572847414456.png)]

三、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,保证线程之间修改的可见性。

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值