ArrayList源码解析

ArrayList 简介
ArrayList源码分析
    继承结构
    类中的属性
    核心方法
        创建集合
        添加集合元素
        删除集合元素
        修改集合元素
        查找集合元素
总结

序言:ArrayList是Java集合中的基础中的基础,也是面试中常问常考的点,今天我们来点简单的,盘盘ArrayList

下面我凭自己的经验大概说一下如何看源码。

先看继承结构,方便了解整体的逻辑和关系
再看核心调用方法,看它做了什么,方便了解核心的功能
得有看英文注释的习惯,毕竟它相当于说明书

注:本篇文章所有分析,针对 jdk1.8版本

ArrayList 的本质是可以动态扩容的数组集合,是基于数组去实现的List类
ArrayList 的底层的数据结构是 Object[] 类型的数组,默认创建为空数组,采用懒加载的方式节省空间,每次采取1.5倍扩容的方式
ArrayList 是线程不安全的,线程安全的 List 参考 Collections.synchronizedList() 或者 CopyOnWriteArrayList

大致结构如上图,先考虑三个问题:

1.为什么要继承 AbstractList,而让 AbstractList 去实现 List , 并不是直接实现呢 ?

这里主要运用了模板方法设计模式,设想一下,jdk 开发作者 还有一个类要实现叫 LinkedList ,它也有何 ArrayList 相同功能的基础方法,但是由于是直接实现接口的方式,这个 类 LinkedList 就没法复用之前的方法了

2.ArrayList 实现了哪些其他接口,有什么用 ?

RandomAccess 接口: 是一个标志接口, 表明实现这个这个接口的 List 集合是支持快速随机访问的。也就是说,实现了这个接口的集合是支持 快速随机访问 策略的,实现了此接口 for循环的迭代效率,会高于 Iterator

Cloneable 接口: 实现了该接口,就可以使用 Object.Clone()方法

Serializable 接口:实现序列号接口,表明该类可以被序列化

  1. 为什么AbstractList 已经实现了 List接口了,作为父类ArrayList 再去实现一遍呢 ?

作者说这是一个错误,一开始拍脑袋觉得可能有用,后来没用到,但没什么影响就留到了现在

public class ArrayList extends AbstractList
implements List, 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 = {};
//缺省空对象数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//集合容器,transient标识不被序列化
transient Object[] elementData;
//容器实际元素长度
private int size;

}

  1. 使用无参构造器

    /**

    • 默认无参构造器创建的时候其实只是个空数组,使用懒加载节省内存开销
    • Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
      */
      public ArrayList() {
      this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
      }
  2. 创建时,指定集合大小,如何一开始就能预估存储元素的size,推荐用此种方式

public ArrayList(int initialCapacity) {
/**
* 如果initialCapacity > 0,就申明一个这么大的数组
* 如果initialCapacity = 0,就用属性 EMPTY_ELEMENTDATA
* Object[] EMPTY_ELEMENTDATA = {}
* 如果initialCapacity < 0,不合法,抛异常
*/
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}

可以看出有四个添加的方式,我们挑选前两个常用方法讲解源码,其他基本差不多

  1. boolean add(E) 默认在末尾插入元素

/**

  • 添加一个元素到list的末端
    */
    public boolean add(E e) {
    //确保内置的容量是否足够
    ensureCapacityInternal(size + 1);
    //如果足够添加加元素放进数组中,size + 1
    elementData[size++] = e;
    //返回成功
    return true;
    }

//minCapacity = size + 1
private void ensureCapacityInternal(int minCapacity) {
//确认容器容量是否足够
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

//先计算容量大小,elementData是初始创建的集合容器,minCapacity= size + 1
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断初始容器是否等于空
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//为空,就拿size + 1和默认容量10比,谁大返回谁
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//否则返回size + 1
return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
//列表结构被修改的次数,用户迭代时修改列表抛异常(ConcurrentModifyException)
modCount++;
/**
* minCapacity 如果超过容器的长度,则进行扩容,两种场景
* 1.第一次添加元素,minCapacity = size + 1,size = 0,在上个方法中会
* 与默认的容器容量10进行大小比较最后为10,那么10 > 0,初始化容器
* 2.添加到第10个元素,上个方法则返回 size + 1 = 11,11 > 10,需要扩容
* 不然数组装不下了
*/
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

//容器的扩容方法
private void grow(int minCapacity) {
//将扩充前的容器长度赋值给oldCapacity
int oldCapacity = elementData.length;
//扩容后的newCapacity = 1 + 1/2 = 1.5,所以为1.5倍扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
/**
* 适用于初始创建,elementData为空数组,length = 0
* oldCapacity = 0,但minCapacity = 10,此处则进行了初始化
*/
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量超过容量上限Integer.MAX_VALUE - 8,则会再尝试扩容
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//新的容量确定后,只需要将旧元素全部拷贝进新数组就可以了
elementData = Arrays.copyOf(elementData, newCapacity);
}

//此方法主要是给集合赋最大容量用的
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
/**
* 这一步无非再判断一次
* size + 1 > Integ er.MAX_VALUE - 8 ?
* 如果大的话赋值 Integer.MAX_VALUE
* 说明集合最大也就 Integer.MAX_VALUE的长度了
*/
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

  1. void add(int,E);在指定位置添加元素

public void add(int index, E element) {
//边界校验 插入的位置必须在0-size之间
rangeCheckForAdd(index);
//不分析了上面有
ensureCapacityInternal(size + 1);
//就是将你插入位置的后面的数组往后移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//放入该位置
elementData[index] = element;
//元素个数+1
size++;
}

private void rangeCheckForAdd(int index) {
//插入位置过于离谱,反手给你一个越界异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

/**

  • src:源对象
  • srcPos:源对象对象的起始位置
  • dest:目标对象
  • destPost:目标对象的起始位置
  • length:从起始位置往后复制的长度
    */
    public static native void arraycopy(Object src, int srcPos, Object dest,
    int destPos, int length)

总结:

初始容量为10,1.5倍扩容,扩容最大值为 Integer.MAX_VALUE,

当我们调用add方法时,实际方法的调用如下:

因为这几个都基本类似,我们就选两个进行剖析

  1. remove(int):通过删除指定位置上的元素

public E remove(int index) {
//检测index,是否在索引范围内
rangeCheck(index);
//列表结构被修改的次数,用户迭代时修改列表抛异常
modCount++;
//取出要被删除的元素
E oldValue = elementData(index);
//和添加的逻辑相似,算数组移动的位置
int numMoved = size - index - 1;
//大于0说明不是尾部
if (numMoved > 0)
//将删除位置后面的数组往删除位前移一格
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
/*
* 由于只是将数组前移盖住了一位,但是长度没变
* 如果删除位置不是末尾,此时末尾会有两个相同元素
* 需要删除末尾最后一个元素,size - 1
*/
elementData[–size] = null; // clear to let GC do its work
//返回要删除的元素值
return oldValue;
}

/*
* 这个方法不过多解释了,但是笔者认为这小小的检查边界的方法细节写的不是很完美
* 这个地方没有主动考虑,index < 0的情况,异常是后续直接抛的,并不是在这
* 后续版本似乎对这个细节做了纠正
*/
private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
  1. removeAll(collection c):批量删除元素

public boolean removeAll(Collection<?> c) {
//判断非空
Objects.requireNonNull©;
//批量删除
return batchRemove(c, false);
}

private boolean batchRemove(Collection<?> c, boolean complement) {
//用一个引用变量接收容器
final Object[] elementData = this.elementData;
//r代表elementData循环次数,w代表不需要删除的元素个数
int r = 0, w = 0;
//修改变动默认为false
boolean modified = false;
try {
//遍历集合,遍历几次r就是几,直至r=size
for (; r < size; r++)
//查看当前集合元素是否不被包含,complement = false
if (c.contains(elementData[r]) == complement)
//不被包含,那我在当前容器从头开始放,w是下标
elementData[w++] = elementData[r];
} finally {
/**
* 如果r没有成功遍历完,抛异常了(这是个finally)
* 理所当然将后面没有遍历完的元素归为不被包含
*/
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
//存在不需要被删除的元素
if (w != size) {
//遍历删除
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
//有做过修改变动
modified = true;
}
}
return modified;
}

public E set(int index, E element) {
//边界范围校验
rangeCheck(index);
//取出旧值
E oldValue = elementData(index);
//赋上新值
elementData[index] = element;
//返回旧值
return oldValue;
}

public E get(int index) {
//边界范围校验
rangeCheck(index);
//通过下标获取查找的元素
return elementData(index);
}

线程不安全
本质是数组,默认长度10,扩容核心方法为grow() ,每次扩容1.5倍,最大容量为 Integer.MAX_VALUE
实现了RandomAccess接口,所以for循环比迭代器循环效率更高
因为底层是数组,所以查询快,修改快,增删慢
ArrayList是Java集合框架中的一个类,它实现了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. 总结 ArrayList是Java集合框架中的一个类,它实现了List接口,可以用来存储一组对象。ArrayList源码解析包括成员变量、构造方法和常用方法。掌握ArrayList源码可以帮助我们更好地理解它的实现原理,从而更加灵活地应用它。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值