ArrayList源码分析笔记

先贴出ArrayList一些属性

  1. public class ArrayList<E> extends AbstractList<E>
  2. implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  3. {
  4. /**
  5. * 系列化ID.
  6. */
  7. private static final long serialVersionUID = 8683452581122892189L;
  8. /**
  9. * 默认初始化容量.
  10. */
  11. private static final int DEFAULT_CAPACITY = 10;
  12. /**
  13. * 用于空实例的共享空数组实例.
  14. */
  15. private static final Object[] EMPTY_ELEMENTDATA = {};
  16. /**
  17. * 共享的空数组实例,用于默认大小的空实例。我们将此与EMPTY_ELEMENTDATA区别开来,
  18. 以了解添加第一个元素时需要膨胀多少。
  19. */
  20. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  21. /**
  22. * 存储ArrayList的元素的数组缓冲区。 ArrayList的容量是此数组缓冲区的长度。添加第
  23. 一个元素时,任何具有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空
  24. ArrayList都将扩展为DEFAULT_CAPACITY。
  25. */
  26. transient Object[] elementData;
  27. /**
  28. * ArrayList的大小(它包含的元素数).
  29. *
  30. * @serial
  31. */
  32. private int size;

以上属性注释都已经被翻译成中文,通过这些注释,我们大概能了解到的一些有价值的信息

  • ArrayList底层数据结构是一个Object数组

  • ArrayList的默认初始化容量为10

  • 一个空的ArrayList集合在添加第一个元素时被扩容为10

  • ArrayList的大小是通过一个名为size的int变量存储的

源码继续往下看,先看ArrayList的构造函数

  1. public ArrayList(int initialCapacity) {
  2. if (initialCapacity > 0) {
  3. this.elementData = new Object[initialCapacity];
  4. } else if (initialCapacity == 0) {
  5. this.elementData = EMPTY_ELEMENTDATA;
  6. } else {
  7. throw new IllegalArgumentException("Illegal Capacity: "+
  8. initialCapacity);
  9. }
  10. }
  11. /**
  12. * 通过这里可以看出,当调用ArrayList的无参构造函数时,这个ArrayList的Object数组被初始化为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA=10
  13. */
  14. public ArrayList() {
  15. this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  16. }
  17. /**
  18. * Constructs a list containing the elements of the specified
  19. * collection, in the order they are returned by the collection's
  20. * iterator.
  21. *
  22. * @param c the collection whose elements are to be placed into this list
  23. * @throws NullPointerException if the specified collection is null
  24. */
  25. public ArrayList(Collection<? extends E> c) {
  26. elementData = c.toArray();
  27. if ((size = elementData.length) != 0) {
  28. // c.toArray might (incorrectly) not return Object[] (see 6260652)
  29. if (elementData.getClass() != Object[].class)
  30. elementData = Arrays.copyOf(elementData, size, Object[].class);
  31. } else {
  32. // replace with empty array.
  33. this.elementData = EMPTY_ELEMENTDATA;
  34. }
  35. }

可以看到ArrayList一共有三个构造函数,一个无参构造,两个有参构造。

当我们在创建一个Arraylist集合时,如果不指定容量,也就是调用无参构造函数,那么这个Arraylist会被初始化为10.

  1. public ArrayList() {
  2. this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  3. }

另外注意到另外两个有参构造,一个参数为int initialCapacity,也就是我们指定的初始化容量,这里对这个initialCapacity进行了一些简单的验证

  1. public ArrayList(int initialCapacity) {
  2. if (initialCapacity > 0) {//initialCapacity > 0
  3. this.elementData = new Object[initialCapacity];//将一个容量为initialCapacity的新Object数组赋给elementData。
  4. } else if (initialCapacity == 0) {//initialCapacity==0
  5. this.elementData = EMPTY_ELEMENTDATA;//将EMPTY_ELEMENTDATA这个空object赋给elementData
  6. } else {//其他情况,也就是小于0,直接抛异常
  7. throw new IllegalArgumentException("Illegal Capacity: "+
  8. initialCapacity);
  9. }
  10. }

另一个有参构造

  1. public ArrayList(Collection<? extends E> c) {
  2. elementData = c.toArray();//将Collection集合转化为数组,然后赋给elementData这个object数组
  3. if ((size = elementData.length) != 0) {//如果这个集合长度不为0
  4. // c.toArray might (incorrectly) not return Object[] (see 6260652)
  5. if (elementData.getClass() != Object[].class)//如果集合转化后的数组不是Object数组
  6. elementData = Arrays.copyOf(elementData, size, Object[].class);//转化为Object数组
  7. } else {//其他情况,也就是elementData的长度为0嘛
  8. // 替换为EMPTY_ELEMENTDATA空的数组
  9. this.elementData = EMPTY_ELEMENTDATA;
  10. }
  11. }

可以看到这个构造函数,它是直接把一个Collection丢了进去,这也就是说,在new一个ArrayList时我们可以把一个Collection集合放到参数列表中。

接下来再来看ArrayList的add方法

  1. /**
  2. * 将元素添加到列表的末尾
  3. *
  4. * @param e 要被追加到list中的元素
  5. * @return <tt>true</tt> (as specified by {@link Collection#add})
  6. */
  7. public boolean add(E e) {
  8. ensureCapacityInternal(size + 1); // Increments modCount!!
  9. elementData[size++] = e;//将元素e添加到数组中,下标为size++,其实也就是size,然后添加后size+1,
  10. return true;
  11. }

这里就两行代码,调用ensureCapacityInternal这个方法,并且参数为size+1。点进去看下ensureCapacityInternal()这个方法

  1. /*
  2. 通过这个方法我们可以看出,当我们第一次调用add方法的时候,elementData数组的size为0,那么size+1就为1,所以minCapacity也为1,这里先通过一个if判断,判断minCapacity是不是一个空的object数组,如果是的话,minCapacity就取DEFAULT_CAPACITY和minCapacity的最大值,也就是10嘛
  3. */
  4. private void ensureCapacityInternal(int minCapacity) {
  5. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  6. minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  7. }
  8. //判断完之后进入了这个方法,同时参数为minCapacity,如果是第一次调用add方法的话参数为minCapacity就是默认大小10,不是第一次调用的话,就是size+1,
  9. ensureExplicitCapacity(minCapacity);
  10. }
  11. private void ensureExplicitCapacity(int minCapacity) {
  12. modCount++;
  13. //这个grow()就是扩容函数,现在minCapacity的话,还是一样的分析,如果第一次调用add方法的话就是10,同时elementData.length为0,不是第一次调用的话minCapacity就是size+1,elementData.length也就是数组的长度为size
  14. if (minCapacity - elementData.length > 0)
  15. grow(minCapacity);
  16. }

读完这一段源码,可以得出以下结论

  • 当我们创建ArrayList时,如果不指定ArrayList的大小,那么第一次调用add方法时,底层会调用扩容方法grow()对Arraylist进行扩容
  • 当ArrayList里面已经存满了元素的时候,再调用add方法,此时会底层会调用grow方法进行扩容,比如ArrayList的大小为10,里面也已经有10个元素了,当我们再调用add方法向里面添加元素的时候,此时Arraylist会触发扩容。

接下来我们再来看下grow方法,了解一些Arraylist到底是怎么样扩容的

  1. private void grow(int minCapacity) {
  2. // overflow-conscious code
  3. int oldCapacity = elementData.length;
  4. int newCapacity = oldCapacity + (oldCapacity >> 1);
  5. //如果数组当前长度+数组当前长度/2 < 数组当前元素个数+1
  6. if (newCapacity - minCapacity < 0)
  7. //就把数组当前元素个数+1赋给newCapacity
  8. newCapacity = minCapacity;
  9. if (newCapacity - MAX_ARRAY_SIZE > 0)
  10. newCapacity = hugeCapacity(minCapacity);
  11. // 最后我们可以看到,这里进行了一个复制操作,将elementData里面的元素,复制到一个新的长度为newCapacity的Object数组,然后再把它赋给elementData
  12. elementData = Arrays.copyOf(elementData, newCapacity);
  13. }

这段代码应该很清晰吧,grow方法传进来的参数就是上面minCapacity,第一次调用add方法时是10,其他时候调用时是size+1(当前存储元素个数+1)

这里简单来说就是将elementData当前长度给oldCapacity,然后newCapacity = oldCapacity + oldCapacity >> 1(左移操作,相当于除于2),如果oldCapacity = 10的话,newCapacity = 10 +(10/2),也就是15。通过以上代码,不难看出,ArrayList的扩容方法实际上就是将底层Object数组复制到了一个新的长度更长的数组里面去,而这个新数组的长度是原来的数组长度+原来数组长度的二分之一,其实就可以说了扩容了1.5倍。

通过对以上源码进行分析,我们可以得出以下结论了

总结:

  • ArrayList底层数据数据结构是一个Object类型的数组
  • ArrayList的默认初始化容量为10
  • 当我们在创建ArrsyList时不指定ArrsyList初始大小,第一次调用add方法时扩容为初始化大小10
  • ArrayList的大小是通过一个名为size的int变量存储的
  • ArrayList有一个构造函数允许Collection集合作为参数,并且会将这个Collection集合转化为数组
  • ArrayList在添加第elementData.length+1个元素时会发生扩容,比如数组长度为10,在添加第11个元素时扩容
  • ArrayList扩容大小为原来的1.5倍,底层实现是通过原来数组长度+原来数组长度左移1位完成,而不是直接通过乘法

ArrayList源码分析笔记的更多相关文章

  1. ArrayList源码分析笔记(jdk1.8)

    1.特点: ArrayList 是一个动态数组,它是线程不安全的,允许元素为null 可重复,插入有序 读写快,增删慢 扩容:默认容量 10,默认扩容1.5倍 建议指定容量大小,减少扩容带来的性能消耗 ...

  2. zeromq源码分析笔记之线程间收发命令(2)

    在zeromq源码分析笔记之架构说到了zmq的整体架构,可以看到线程间通信包括两类,一类是用于收发命令,告知对象该调用什么方法去做什么事情,命令的结构由command_t结构体确定:另一类是socke ...

  3. Java集合干货——ArrayList源码分析

    ArrayList源码分析 前言 在之前的文章中我们提到过ArrayList,ArrayList可以说是每一个学java的人使用最多最熟练的集合了,但是知其然不知其所以然.关于ArrayList的具体 ...

  4. ArrayList 源码分析

    ArrayList 源码分析 1. 结构   首先我们需要对 ArrayList 有一个大致的了解就从结构来看看吧. 1. 继承   该类继承自 AbstractList 这个比较好说 2. 实现 这 ...

  5. ArrayList源码分析超详细

    ArrayList源码分析超详解 想要分析下源码是件好事,但是如何去进行分析呢?以我的例子来说,我进行源码分析的过程如下几步: 找到类:利用 IDEA 找到所需要分析的类(ztrl+N查找ArraLi ...

  6. Java - ArrayList源码分析

    java提高篇(二一)-----ArrayList 一.ArrayList概述 ArrayList是实现List接口的动态数组,所谓动态就是它的大小是可变的.实现了所有可选列表操作,并允许包括 nul ...

  7. java集合系列之ArrayList源码分析

    java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...

  8. ArrayList源码分析超详细(转载)

    ArrayList源码分析超详细   ArrayList源码分析超详解 想要分析下源码是件好事,但是如何去进行分析呢?以我的例子来说,我进行源码分析的过程如下几步: 找到类:利用 IDEA 找到所需要 ...

  9. ArrayList源码分析--jdk1.8

    ArrayList概述   1. ArrayList是可以动态扩容和动态删除冗余容量的索引序列,基于数组实现的集合.  2. ArrayList支持随机访问.克隆.序列化,元素有序且可以重复.  3. ...

随机推荐

  1. 浅谈Webpack模块打包工具四

    Webpack 生产环境优化 生产环境和开发环境有很大的差异,生产环境只注重运行效率,开发环境主要开发效率,webpack4.0开始提出了(mode)模式的概念 针对不同的环境进行不同的配置,为不同的 ...

  2. select(),fd_set(),fd_isset()

    1. select函数 1. 用途 在编程的过程中,经常会遇到许多阻塞的函数,好像read和网络编程时使用的recv, recvfrom函数都是阻塞的函数,当函数不能成功执行的时候,程序就会一直阻塞在 ...

  3. Leetcode(11)-盛最多水的容器

    给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) .画 n 条垂直线,使得垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0).找出其中的两条线,使得 ...

  4. 杭电多校HDU 6599 I Love Palindrome String (回文树)题解

    题意: 定义一个串为\(super\)回文串为: \(\bullet\) 串s为主串str的一个子串,即\(s = str_lstr_{l + 1} \cdots str_r\) \(\bullet\ ...

  5. Gym 101174D Dinner Bet(概率DP)题解

    题意:n个球,两个人每人选C个球作为目标,然后放回.每回合有放回的拿出D个球,如果有目标球,就实现了这个目标,直到至少一个人实现了所有目标游戏结束.问结束回合的期望.误差1e-3以内. 思路:概率DP ...

  6. Doris开发手记1:解决蛋疼的MySQL 8.0连接问题

    笔者作为Apache Doris的开发者,平时感觉相关Doris的文章写的很少.主要是很多时候不知道应该去记录一些怎么样的问题,感觉写的不好就会很慌张.新的一年,希望记录自己在Doris开发过程之中所 ...

  7. Smashing Conf 2020

    Smashing Conf 2020 https://smashingconf.com/online-workshops/ events https://smashingconf.com/ny-202 ...

  8. HTML5 Server-Sent Events

    HTML5 Server-Sent Events SSE demo https://www.w3schools.com/html/tryit.asp?filename=tryhtml5_sse htt ...

  9. SQL Server中DELETE和TRUNCATE的区别

    ​DELETE和TRUNCATE语句之间的区别是求职面试中最常见的问题之一.这两条语句都可以从表中删除数据.然而,也有不同之处. 本文将重点讨论这些差异,并通过实例加以说明. TRUNCATE DEL ...

  10. WPF MVVM实例三

    在没给大家讲解wpf mwm示例之前先给大家简单说下MVVM理论知识: WPF技术的主要特点是数据驱动UI,所以在使用WPF技术开发的过程中是以数据为核心的,WPF提供了数据绑定机制,当数据发生变化时 ...