一、ArrayList源码解读

ArrayList源码

一、前言

ArrayList在日常的开发中使用频率非常高,但是JDK是如何去设计ArrayList的,这就需要我们好好去了解底层实现原理,这样使用起来才能做到心中有数;当然,还能应付面试。本篇文章会围绕ArrayList的核心方法进行源码解读,希望对你有所帮助。
在这里插入图片描述

二、提取官方文档核心技术点

在这里插入图片描述

  1. ArrayList是由可调整大小数组实现。并允许添加所有元素,包括 null
  2. ArrayList实现 List 接口之外,自己还提供了操作内部用于存储列表的数组大小的方法。(此类大致等同于 Vector,只是它是不同步的)
  3. size、isEmpty、get、set、iterator 和 listIterator 操作在常量时间内运行添加操作在摊销常量时间内运行,即添加 n 个元素需要 O(n) 时间。所有其他操作都以线性时间运行(粗略地说)
  4. 每个 ArrayList 实例都有一个 容量。容量是用于在列表中存储元素的数组的大小。它始终至少与列表大小一样大。当元素添加到 ArrayList 时,其容量会自动增长。除了添加元素具有恒定的摊销时间成本这一事实之外,没有指定增长策略的详细信息。
  5. 应用程序可以在使用 ensureCapacity 操作添加大量元素之前增加 ArrayList 实例的容量。这可能会减少增量重新分配的数量。请注意,此实现不同步。
  6. 如果多个线程同时访问 ArrayList 实例,并且至少有一个线程在结构上修改了该列表, 则必须在外部同步该列表。(结构修改指的是添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅设置元素的值不是结构修改。这通常是通过在自然封装列表的某个对象上进行同步来实现的。如果不存在此类对象,则应使用该方法**“ Collections.synchronizedList 包装”**列表。最好在创建时执行此操作,以防止意外地对列表进行不同步访问:
    List list = Collections.synchronizedList(new ArrayList(…));
  7. 此类返回的迭代器 iterator 并且 listIterator 方法是 快速失败的:如果在创建迭代器后的任何时间对列表进行结构修改,则除了通过迭代器自己的 remove OR add 方法之外,迭代器将抛出 ConcurrentModificationException.因此,面对并发修改,迭代器会快速而干净地失败,而不是冒着在未来不确定的时间出现任意、非确定性行为的风险。
  8. 请注意,无法保证迭代器的快速故障行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException 。因此,编写一个依赖于此异常来判断正确性的程序是错误的;迭代器的快速故障行为应仅用于检测错误

三、前置知识

在ArrayList中在进行扩容、删除值、增加值的操作都会使用到数组复制操作,在源码中会涉及到两种:

  • System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)-System类提供的native方法
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);// 表示从src的srcPos位置拷贝length个值到dest从destPos开始的位置
  • Arrays.copyOf(elementData, newCapacity)- Arrays类提供的方法
public static <T> T[] copyOf(T[] original, int newLength) {
   // 传递一个original数组和一个newLength最后返回一个新数组
    return (T[]) copyOf(original, newLength, original.getClass());
}

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
   
  @SuppressWarnings("unchecked")
  T[] copy = ((Object)newType == (Object)Object[].class)
    ? (T[]) new Object[newLength] // 直接new一个newLength长度的数组
    : (T[]) Array.newInstance(newType.getComponentType(), newLength);
  System.arraycopy(original, 0, copy, 0,
                   Math.min(original.length, newLength));// 使用arraycopy执行copy操作
  return copy;// 返回新数组
}

四、继承体系

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

在这里插入图片描述

从上面的继承关系中可以看出,ArrayList至少有以下几大特性:

  1. 实现Cloneable接口,说明是支持克隆的
  2. 继承AbstractList得到AbstractList封装的通用逻辑
  3. 实现RandomAccess接口,说明支持快速随机访问,下面会介绍
  4. 实现Serializable接口,说明支持序列化和反序列化

五、RandomAccess接口

public interface RandomAccess {
   
}

淦。发现真的只是一个接口然后什么都没了,看看官方解释:

List实现使用RandomAccess标记接口表明它们支持快速(通常是恒定时间)随机访问。此接口的主要目的是允许通用算法在应用于随机或顺序访问列表时改变其行为以提供良好的性能
用于操作随机访问列表(例如ArrayList )的最佳算法在应用于顺序访问列表(例如LinkedList )时会产生二次行为。鼓励通用列表算法在应用算法之前检查给定列表是否是此接口的实例,如果将其应用于顺序访问列表会提供较差的性能,并在必要时更改它们的行为以保证可接受的性能。
众所周知,随机访问和顺序访问之间的区别通常是模糊的。例如,如果某些List实现变得很大,则提供渐近线性访问时间,但在实践中访问时间是恒定的。这样的List实现一般应该实现这个接口。根据经验,如果对于类的典型实例,如果出现以下循环,则List实现应该实现此接口:比如当前要讲的ArrayList使用下面的迭代方式才快。
for (int i=0, n=list.size(); i < n; i++)
list.get(i);

运行速度比这个循环快:【注意:LinkedList使用这种遍历方式更快,因为LinkedList没有继承RandomAccess接口
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();

可以看看ArrayList和LinkedList不同遍历方式的耗时差距:

ArrayList traverse : 13 【普通遍历方式】
ArrayList traverse by iterator : 31 【迭代器方式】
ArrayList traverse by foreach : 34 【foreach方式】

LinkedList traverse : 6422 【普通遍历方式】
LinkedList traverse by iterator: 19【迭代器方式】
LinkedList traverse by foreach : 7【foreach方式】

六、核心变量

// 默认容量大小为10
private static final int DEFAULT_CAPACITY = 10;
// 【使用有参构造的方式,但是initialCapacity为0的时候】对应的空数组
private static final Object[] EMPTY_ELEMENTDATA = {
   };
// 默认容量下【使用无参构造的方式】指向的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
   };
// 存放数据的数组
transient Object[] elementData; // non-private to simplify nested class access
// 当前list中存放值的个数
private int size;
// 支持的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

七、构造方法

无参构造

public ArrayList() {
   
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;// 指向默认的空数组,注意:DEFAULTCAPACITY_EMPTY_ELEMENTDATA和上面的EMPTY_ELEMENTDATA在扩容的时候就能看出这两个之间的区别
}

有参构造

public ArrayList(int initialCapacity) {
   
    if (initialCapacity > 0) {
   
        this.elementData = new Object[initialCapacity];// 直接new一个长度为initialCapacity的数组
    } else if (initialCapacity == 0) {
   
        this.elementData = EMPTY_ELEMENTDATA;// 标记空数组,elementData指向EMPTY_ELEMENTDATA
    } else {
   // 如果initialCapacity小于0直接抛异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

// 参数为一个Collection
public ArrayList(Collection<? extends E> c) {
   
  elementData = c.toArray();// elementData直接指向一个数组
  if ((size = elementData.length) != 0) {
   
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    // 源码中已经说了,c.toArray返回的值有可能不是Object[]类型
    if (elementData.getClass() != Object[].class)
      elementData = Arrays.copyOf(elementData, size, Object[].class);// 将elementData转换为Object[]类型
  } else {
   // 说明elementData的size为0
    this.elementData = 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值