ArrayList源码解析

这篇博客主要是用来对ArrayList的源码解析,相信大家在工作中对ArrayList的使用应该是非常多的,下面我将详细分析他们源码,看能否帮大家查漏补缺,作者使用的IDE是IntelliJ,jdk版本是1.8,建议读者也用相同的环境打开源码跟着一起分析,下面正式进入主题:

首先我们先看下ArrayList的类关系图:
在这里插入图片描述
从图中可以看出,ArrayList主要是继承了AbstractList抽象类,其余的实现类我们讲方法的时候讲到了会带过,下面我们简单看下这个类里面的几个方法:

 // add 方法
 public boolean add(E e) {
     add(size(), e);
     return true;
 }
 public void add(int index, E element) {
    throw new UnsupportedOperationException();
 }
 // get 方法
 abstract public E get(int index);
 // set 方法
 public E set(int index, E element) {
     throw new UnsupportedOperationException();
 }
 // remove 方法
 public E remove(int index) {
     throw new UnsupportedOperationException();
 }

从上面的代码段可以看出来,AbstractList是提供了一个类似于模板模式的一些方法,并且有些方法是直接抛错的,强制需要继承类自己实现这一点和AQS是一样的设计思想(实际上java.util下面的很多都是这样的设计思想,多看我两篇文章就知道啦),既然大概了解了他的父类方法,我们就来看看ArrayList是怎么实现的吧!
首先我们看下ArrayList的几个属性

 // 序列化版本号(因为实现了java.io.Serializable)
 private static final long serialVersionUID = 8683452581122892189L;
 // 默认初始化容量
 private static final int DEFAULT_CAPACITY = 10;
 // 空数组
 private static final Object[] EMPTY_ELEMENTDATA = {};
 // 默认容量空数组
 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 // 存储元素的数组(不可序列化)
 transient Object[] elementData; // non-private to simplify nested class access
 // 当前ArrayList的元素个数
 private int size;

接着我们来看下ArrayList的构造方法:

 // 1、无参构造器,初始化存储元素的数组为默认容量空数组
 public ArrayList() {
       this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
 }
 // 2、 带容量参数的构造器,主要是用来指定大小的ArrayList的初始化
 public ArrayList(int initialCapacity) {
 		// 传入参数大于0时初始化一个同等大小的Object数组赋值给elementData 
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        	// 传入参数等于0时使用空数组赋值
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
        	// 传入参数小于0时直接抛错
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
 }
 // 3、传入Collection集合初始化ArrayList
 public ArrayList(Collection<? extends E> c) {
 		// 调用toArray()方法(见下方),将其转换为object()数组
        elementData = c.toArray();
        // 如果传入的集合不为空走if逻辑,else就会初始化一个空数组
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            // 解决bug问题,由于其c.toArray()可能出现返回值不为Object[]的错误。
            // 因为使用toArray的方法有所不同,向上转型赋值可能会报错,所以采用如下方法,该bug在jdk1.9里面修复了
            if (elementData.getClass() != Object[].class)
                //使用数组拷贝来进行
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
 }

好啦,构造方法讲完了,那我们就开始讲一下我们常用的API吧,首先讲add(E element)方法

 // add 方法,非线程安全,因为肯那个存在同一个size被多个线程抢到去add的时候会产生数据丢失的情况
 public boolean add(E e) {
 		// 判断容量是否满足,size前面讲属性的时候初始化是0的
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
 }
 // 我们一步步分析他是如果判断容量是否满足和扩容的
 private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
 }
 
 private static int calculateCapacity(Object[] elementData, int minCapacity) {
 		// 返回最大容量 第一次无参构造的话返回的是 10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
 }
 private void ensureExplicitCapacity(int minCapacity) {
 		// 这里使用了一个Fail-Fast 机制 ,快速失败机制,主要是非线程安全的类使用的一种快速报错的手段
        modCount++;
        // 判断现在的容量是否还能容纳新增的这个元素,第一次一般都是会走grow,因为我们一般使用的都是无参构造
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
 }
 // grow 方法是用来做扩容
 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 将容量扩展为现在的1.5倍 oldCapacity + (oldCapacity >> 1) “>>1” 是右移一位就是除以2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 扩容之后的新容量如果还小于minCapacity则将minCapacity赋值给他,第一次无参构造的add进来时赋值的10
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        	// 当扩容长度超过了最大长度限制则返回Integer.MAX_VALUE
            newCapacity = hugeCapacity(minCapacity);
        // 这里调用Arrays.copyOf点进去看的话会发现是调用的native方法,那么问题来了native在jvm内存模型里是在哪个模块呢?
        elementData = Arrays.copyOf(elementData, newCapacity);
        
   // 好了,我们回到add方法
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 将value e放进elementData[size]中,然后size = size+1
        elementData[size++] = e;
        return true;
    }
 }

add方法讲完了,我们可以看下相关联的add(int index, E element) ,这个方法和add(E element) 区别不大,主要是多了个数组越界检查,和指定index赋值:

 public void add(int index, E element) {
 		// 数组越界检查,这里为啥要用size做检查而不用elementData.length做检查呢?
 		// 我猜想是因为数组在内存里是连续空间,新增需要对其他元素后移,如果是新增的不是<=size+1的位置,那么就会出现断层
        rangeCheckForAdd(index);
        // 检查是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // native 方法扩容,需要从index开始重新后移
        // 这里需要注意 add(int index, E element)会后移Index及index以后的所有数据至新的index以后
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // 真正替换在这里
        elementData[index] = element;
        size++;
 }

还有个 addAll(Collection<? extends E> c)这个方法,这个方法也很简单

public boolean addAll(Collection<? extends E> c) {
		// 转为object数组
        Object[] a = c.toArray();
        int numNew = a.length;
        // 扩容
        ensureCapacityInternal(size + numNew);  // Increments modCount
        // 直接不管c.toArray()这个bug,使用通用的native方法赋值
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

下面讲讲get方法,比较简单

 public E get(int index) {
		// 数组越界检查,这里用的>=size 跟上面add方法的检查不一样,为什么?因为add是可以新增一个的而这个是访问所以不能越界
        rangeCheck(index);
        // 返回index数据
        return elementData(index);
 }

好了,到了常用方法的最后一个了remove方法

// 根据下标删除
public E remove(int index) {
		// 数据越界检查,同上
        rangeCheck(index);
        // 快速失败
        modCount++;
        E oldValue = elementData(index);
        int numMoved = size - index - 1;
        // 这里是判断是否需要前移,>0 证明删除的index不是最后一个,所以需要前移(因为数组是连续的)
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // 手动置为null,方便gc

        return oldValue;
    }
// 根据对象删除
public boolean remove(Object o) {
		// 遍历,然后调用快速删除
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                	// 跟上面的下标删除不一样的是,快速删除没有判断数组越界,因为这里已经有判断了
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
 }

然后我们讲一讲前面提到的fail-fast modCount这个属性吧,这里主要用在常用的场景里,比如说sort,removeIf,forEach,只要被改变就会报错 ConcurrentModificationException并发修改异常。
好啦,今天的源码解析就到这里了,我们来总结下 ArrayList的特点吧:
1、ArrayList内部是Object[]数组,然后使用的时候进行动态扩容。ArrayList默认会分配10个元素的数组,然后在此基础上进行扩容,每次新的扩容后的数组长度是原数组长度的1.5倍。如果你大概知道集合的容量,可以指定初始化值,减少扩容带来性能损耗。
2、查找和修改性能强大,数组查找连续内存,速度快。删除操作相对较慢,绝大部分需要移动数组
3、非线程安全,在多线程里面使用需要加锁或者使用线程安全的容器比如CopyOnWriteArrayList

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ArrayListJava集合框架中的一个类,它实现了List接口,可以用来存储一组对象,这些对象可以是任意类型。 下面是ArrayList源码解析: 1. 成员变量 ```java /** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size; ``` ArrayList有三个成员变量,分别是DEFAULT_CAPACITY、EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA。DEFAULT_CAPACITY表示默认的容量大小,EMPTY_ELEMENTDATA是一个空数组,DEFAULTCAPACITY_EMPTY_ELEMENTDATA也是一个空数组,但它会在第一次添加元素时扩容为DEFAULT_CAPACITY大小。elementData是一个Object类型的数组,用于存储ArrayList中的元素,size表示ArrayList中元素的数量。 2. 构造方法 ```java /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // defend against c.toArray (incorrectly) not returning Object[] // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } } /** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } ``` ArrayList提供了三个构造方法。第一个构造方法是无参的构造方法,它将elementData赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA。第二个构造方法接收一个Collection类型的参数c,它将参数c中的元素转为数组并将其赋值给elementData。第三个构造方法接收一个int类型的参数initialCapacity,它根据参数initialCapacity的值创建一个Object类型的数组并将其赋值给elementData。 3. 常用方法 常用方法包括add()、get()、set()、remove()、size()等。 add()方法用于在ArrayList中添加一个元素,如果elementData的容量不足,就需要进行扩容。扩容的方式是将elementData数组的大小增加50%。 get()方法用于获取ArrayList中指定位置的元素。 set()方法用于将ArrayList中指定位置的元素替换为指定的元素。 remove()方法用于删除ArrayList中指定位置的元素。 size()方法用于获取ArrayList中元素的数量。 4. 总结 ArrayListJava集合框架中的一个类,它实现了List接口,可以用来存储一组对象。ArrayList源码解析包括成员变量、构造方法和常用方法。掌握ArrayList源码可以帮助我们更好地理解它的实现原理,从而更加灵活地应用它。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值