ArrayList源码解析(基于JDK1.8)

本文详细解析ArrayList的底层实现,包括其接口实现、成员变量、构造器、容量调整、操作效率及多线程安全等要点。通过源码分析,揭示ArrayList在Java集合框架中的关键特性,如快速访问、克隆和序列化,并讨论了在并发场景下的注意事项。此外,文章还介绍了ArrayList的扩容策略和常用方法,如添加、删除、查找元素的实现细节,并给出了实用建议,如在操作集合时使用Stream API以提高代码质量。
摘要由CSDN通过智能技术生成

ArrrayList是Java中经常被用到的集合,弄清楚它的底层实现,有利于我们更好地使用它。

下图是ArrayList的UML图:
这里写图片描述

从图中我们可以看出:

1: 实现了RandomAccess接口:表面ArrayList支持快速(通常是常量时间)的随机访问。官方源码也给出了解释:(因为底层实现是一个数组,所以get()方法要比迭代器快,后面还会更有更加详细的源码解析)
这里写图片描述
2:实现了Cloneable接口,表明它支持克隆。可以调用clone()进行浅拷贝。
3:实现了Serializable接口,表明它支持序列化。
4:它还实现了List接口,并且继承自AbstractList抽象类。

代码分析部分太多了,我直接把总结弄到最上面了,可以方便查看。

总结:

① ArrayList在我们日常开发中随处可见,所以建议大家可以自己手动实现一个ArrayList,实在写不出来可以模仿一下ArrayList么。

② 由于ArryList随机存储,底层是用的一个数组作为存放元素的,所以在遍历ArrayList的时候,使用get()方法的效率要比使用迭代器的效率高。

③在ArrayList中经常使用的一个变量modCount,它是在ArrayList的父类AbstractList中定义的一个protected变量,该变量主要在多线程的环境下,如果使用迭代器进行删除或其他操作的时候,需要保证此刻只有该迭代器进行修改操作,一旦出现其他线程调用了修改modCount的值的方法,迭代器的方法中就会抛出异常。究其原因还是因为ArrayList是线程不安全的。
④ 在ArrayList底层实现中,很多数组中元素的移动,都是通过本地方法System.arraycopy实现的,该方法是由native修饰的。
⑤ 在学习源码的过程中,如果有看不懂的方法,可以自己写一个小例子调用一下这个方法,然后通过debug的方式辅助理解代码的含义。当然啦,有能力的最好自己实现一下。(不过有些方法确实设计的超级精巧,直接读代码还看不懂,只能通过debug辅助学习源代码,更别提写这些方法了。。。。)
⑥ 不过我们在操作集合的过程中,尽量使用使用基于Stream的操作,这样能够不仅写起来爽,看起来更爽!真的是谁用谁知道。简直不要太爽!

下面是源码解析的部分:

①首先我们先看一下JDK中ArrayList的属性有哪些:

   
   private static final long serialVersionUID = 8683452581122892189L;
	
	//默认的容量
	private static final int DEFAULT_CAPACITY = 10;
    
    //定义了一个空的数组,用于在用户初始化代码的时候传入的容量为0时使用。
    private static final Object[] EMPTY_ELEMENTDATA = {};
	
	//同样是一个空的数组,用于默认构造器中,赋值给顶层数组elementData。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	
	//底层数组,ArrayList中真正存储元素的地方。
    transient Object[] elementData;
     
    //用来表示集合中含有元素的个数
    private int size;

②看看我们的JDK中提供的构造器:(提供了三种构造器,分别是:需要提供一个初始容量、默认构造器、需要提供一个Collection集合。)

//  如果我们给定的容量等于零,它就会调用上面的空数组EMPTY_ELEMENTDATA。
//	如果大于零的话,就把底层的elementData进行初始化为指定容量的数组。
//	当然啦,如果小于零的话,就抛出了违法参数异常(IllegalArgumentException)。
 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);
        }
    }


    /**
     * 默认的情况下,底层的elementData使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组。
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     *
     * 传入一个集合,首先把集合转化为数组,然后把集合的底层数组elementData指向该数组,
     * 此时,底层数组有元素了,而size属性表示ArrayList内部元素的个数,所以需要把底层数组
     * element的大小赋值给size属性,然后在它不等于0 的情况
     * 下(也就是传进来的集合不为空),再通过判断保证此刻底层数组elementData数组的类型
     * 和Object[]类型相同,如果不同,则拷贝一个Object[]类型的数组给elementData数组。
     * 如果参数collection为null的话,将会报空指针异常。
     *
     * @param 一个Collection集合。
     * @throws 如果参数collection为null的话,将会报异常(NullPointerException)
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

③缩至“最简洁”的容量:把ArrayList的底层elementData数组大小调整为size(size是ArrayList集合中存储的元素的个数)

那为什么会有这个方法呢?
因为我们在ArrayList中添加元素的时候,当ArrayList容量不足的时候,ArrayList会自动扩容,(调用的是ensureCapacityInternal()方法,这个方法后续会讲解。),一般扩充为原来容量的1.5倍,我们可能用不了那么多的空间,所以,有时需要这个来节省空间。

//modCount这个变量从字面意思看,它代表了修改的次数。实际上它就是这个意思。
//它是AbstractList中的 protected修饰的字段。
//我们首先解释一下它的含义:顾名思义,修改的次数(好像有点废话了)
//追根溯源还是由于ArrayList是一个线程不安全的类。这个变量主要是用来保证在多线程环境下使用
//迭代器的时候,同时在对集合做修改操作时,同一时刻只能有一个线程修改集合,如果多于一个
//线程进行对集合改变的操作时,就会抛出ConcurrentModificationException。
//所以,这是为线程不安全的ArrayList设计的。
//
//接着判断一下,如果ArrayList中元素的个数小于底层数组的长度,说明此时需要缩容。
//最后通过一个三位运算符判断,如果ArrayList中没有元素,则把底层数组设置为空数组。
//否则的话,就使用数组拷贝把底层数组的空间大小缩为size(元素个数)的大小。
//
  public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

④增加ArrayList实例的容量,如果有需要的话,以确保它至少能容纳元素的数量由传入的参数决定。

//官方的JDK中首先:需要确定一个最小的预期容量(minCapacity):
//它通过判断底层数组是否是DEFAULTCAPACITY_EMPTY_ELEMENTDATA(也就是说是不是使用了
//默认的构造器,),如果没有使用默认的构造器的话,它的最小预期容量是0,如果使用了默认
//构造器,最小预期容量(minCapacity)为默认容量(DEFAULT_CAPA
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值