【源码解析】-- ArrayList

前言

解析 ArrayList 的前提肯定是在我们已经熟悉了 ArrayList 的方法,知晓 ArrayList 数据结构的特性,和使用场景。我们应知道 ArrayList 也只是一个普通的类,我们也可以写出这样的代码,因此不必对源码产生恐惧感。接下来将从两个方面来解析ArrayList源码:Array的基本实现ArrayList 的方法解析。让我们将源码拷贝下来并将注释翻译,从普通类的角度从上至下进行阅读吧。

ArrayList类的继承和实现

首先我们先来观察ArrayList类的签名这一块,我们可以发现 ArrayList继承了抽闲的 AbstractList 实现了 List, RandomAccess, Cloneable, Serializable。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

继承的

打开AbstractList 我们发现AbstractList 是一个抽象类,它声明了一个最小的 List 应有的基本方法,具体方法如下表所示(不包含迭代器):

方法名是否为抽象重载数是否被覆盖
add()2
addALL()0
clear()0
get()0
indexOf()0
remove()0
removeRange()0
set()0
subList()0
listIterator()0
lastIndexOf()0

实现的

AbstractList 实现了RandomAccess标致性接口,表示是支持随机访问的
AbstractList 实现了Cloneable标致性接口,表示是支持可克隆的
AbstractList 实现了Serializable标致性接口,表示是支持可序列化的

Array的基本实现

ArrayList类的属性

打开ArrayList我们可以发现有一些属性,请注意这些属性都是私有的进行修饰的,表示了,这些都是内部的属性不向我们公开,在我们创建数据结构是也要有这样的思维方式。 去掉注释分别是:

根据变量名我们可以得知这是类的序列化版本号,ArrayList是支持序列化的。

private static final long serialVersionUID = 8683452581122892189L;

根据变量名我们可以得知,此属性指定了ArrayList的默认容量

  private static final int DEFAULT_CAPACITY = 10;

通过注释可以得知,这个属性是在ArrayList创建以后在其中没有数据时,在方法中共享的一个空的List集合。

 private static final Object[] EMPTY_ELEMENTDATA = {};

还是一个空的示例,不过用处和目的与上面的属性不相同,为了解添加第一个元素时要膨胀多少。用处不同,使用名称作一区分,

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

这是一个值为空的Obj类型的数组,存储ArrayList元素的数组缓冲区,这个数组的长度决定了ArrayList的容量,非私有以简化嵌套类访问。

transient Object[] elementData;

保存了ArrayList容器内现有元素的个数

 private int size;

ArrayList类的构造方法

ArrayList类有 3 个构造方法 1 个无参数的 2 个有参数的,我们来看看他们都做了什么。

无参数构造方法

阅读此段代码我们发现,在我们new一个无参构造方法的时候,其实将一个有着默认容量(数组的默认容量)的元数据为空的一个数组将其值赋给了elementData属性,elementData属性成为了一个空的有着默认容量的数组,此时elementData这个引用也就指向了DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,elementData从一个为null的属性变成了一个没有元素的实质性的数组

/*构造一个初始容量为 10 的空列表。*/
 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

第一个有参数的构造方法

阅读发现次构造方法需要传入一个 int 类型的初始化容量,为ArrayList指定容量。
如果指定了大于0的初始化容量,则创建一个Obj类型的数组赋给缓冲数组,此时缓冲数组才有了值,不为null了,
如果指定了0 为初始化值,此时我们不能创建化一个没有长度的数组所以java给了我们一个元素为空的且有默认值的数组。
其他初始化值皆为不合法抛出异常。

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);
        }
    }

第二个有参数的构造方法

观察这个构造方法我们发现其参数列表需要一个 Collection 类型的参数,
很明显就是将一个List或者Set转换为一个ArrayList,请看我在代码中添加的注释。

 public ArrayList(Collection<? extends E> c) {
 		//将参数转换为数组,并赋值给elementData 属性使此属性拥有实质。
        elementData = c.toArray();
        //如果得到值的elementData 也就是传入参数的元素个数不为0的话执行以下代码
        if ((size = elementData.length) != 0) {
        	//如果传入的参数不是一个数组类型的话执行以下代码
            if (elementData.getClass() != Object[].class)
            	//将数据拷贝后存入elementData 目的是我们必须得到一个数组。
                elementData = Arrays.copyOf(elementData, size, Object[].class);
                //其他情况则直接放弃参数,给一个空的有着默认大小的数组。
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

小小总结

读完上面这些可以的出结论:
不论使用哪种方式创建ArrayList对象。都会将elementData 属性从其值为null设置为一个Obj类型的数组,并且为size属性赋值,这就证明了ArrayList底层是一个Obj类型的数组,我们最终操作的到ArrayList集合是ArrayList类中的elementData 属性,构造方法的执行完成了ArrayList集合的创建与初始化(容量初始化和元素个数初始化)。

ArrayList 的方法解析

以下可能只分析常用的方法,这根据今天所剩的时间而定,其中不包含Array中的私有方法(私有的方法时为了服务ArrayList内部的各种方法我们开发用不到)。可能有些读者会问:为什么要分析这么简单的方法和分析的这么细节呢,这是为了以后节省时间,和更加细致的了解ArrayList的实现思想与原理,也为后来者省力,正是这样的细致 才能使得我们的代码水平越来越高。

add()方法

一个参数的add()方法:

阅读发现返回值是一个boolean类型需要一个E(Element在集合中使用,因为集合中存放的是元素 ),

 public boolean add(E e) {
 		//ensureCapacityInternal是ArrayList中的方法,
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将参数添加到数组的末尾处(size是元素的个数,也表示了最后一个元素的位置,使用++操作得到第一个空位将e添加到素组的末尾)
        elementData[size++] = e;
        //程序执行到此,添加成功返回true
        return true;
    }

两个参数的add()方法:

两个参数:要插入指定元素的索引,和要添加的元素。

/*在此列表中的指定位置插入指定元素。将当前在该位置的元素(如果有)和任何后续元素向右移动(向它们的索引添加一个)*/
public void add(int index, E element) {
		//ArrayList本类中的方法,
        rangeCheckForAdd(index);
        //因为要添加新元素首先要保证数组的容量要足够
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        /*将指定源数组中的数组从指定位置复制到目标数组的指定位置。将原始的elementData的数据以流的形式考出在重新考入到elementData中,此方法的参数elementData是原始数组,index是源数组中的起始位置
        elementData是新的数组,index + 1是目的地数据中的起始位置,size - index是要复制的数组元素的数量。如下图*/
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //将要添加的数组添加到指定的位置                 
        elementData[index] = element;
        //将ArrayList的元素个数加1
        size++;
    }

在这里插入图片描述

addAll()方法

返回boolean需要Collection类型的参数

/**按照指定集合的​​迭代器返回的顺序,将指定集合中的所有元素追加到此列表的末尾*/
   public boolean addAll(Collection<? extends E> c) {
   		//将参数转换为数组并赋给局部变量a
        Object[] a = c.toArray();
        //获取参数的元素个数将他赋给局部变量numNew 
        int numNew = a.length;
        
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //拷贝数组
        System.arraycopy(a, 0, elementData, size, numNew);
        //将ArrayList的元素个数更新
        size += numNew;
        //如果要添加的元素的个数不为0,那么肯定成功添加了元素返回true
        return numNew != 0;
    }

clear()方法

/*从此列表中删除所有元素(可选操作)*/
 public void clear() {
 		//modCount是父类AbstractList中的一个受保护的属性,AbstractList默认已经继承,为迭代器提供参考,表示了此列表在结构上被修改的次数。结构修改是那些改变列表大小的修改
        modCount++;
        /* 让GC做好自己的工作*/
        //简单粗暴,直接将集合中的每个元素修改为null
        for (int i = 0; i < size; i++)
            elementData[i] = null;
		//更新数据
        size = 0;
    }

contains() 方法

返回boolean 类型,需要Object 类型的参数

/*如果此列表包含指定的元素,则返回 true 。*/
 public boolean contains(Object o) {
 		//如果indexOf不>0则证明有元素,有元素就返回元素
        return indexOf(o) >= 0;
    }

ensureCapacity() 方法

这个方法在AarrayList的添加和插入中很常见
需要一个最小容量参数

/*如果需要,增加此 ArrayList实例的容量,以确保它可以至少保存最小容量的参数所指定的元素数。*/
    public void ensureCapacity(int minCapacity) {
    	//如果ArrayList集合不是初始状态的
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
         	//如果为真则minExpand = 0
            ? 0
    		//如果为假则指定一个默认值10
            : DEFAULT_CAPACITY;
		//如果要扩充的容量大于elementData 的实际容量,则扩展。 
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

get()方法

返回一个元素,需要一个下标

public E get(int index) {
		//如果>=下标值则超出了集合的容量范围 会抛出异常
        rangeCheck(index);
		//执行到这里则表示正常,返回下标所指的元素  
        return elementData(index);
    }

set()方法

返回一个元素,需要下标和元素

/*用指定的元素替换此列表中指定位置的元素。*/
   public E set(int index, E element) {
   		//如果>=下标值则超出了集合的容量范围 会抛出异常
        rangeCheck(index);
		//获取index下标位置旧元素赋值给oldValue局部变量
        E oldValue = elementData(index);
        //将新元素赋给下标为index的数据
        elementData[index] = element;
        返回旧值 
        return oldValue;
    }

retainAll() 方法

返回boolean 类型需要一个Collection

/*仅保留此列表中包含在指定集合中的元素。*/
    public boolean retainAll(Collection<?> c) {
    	//判断c是不为 null
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值