文章目录

  • 注释
  • 类的继承与实现
  • 构造函数
  • add操作
  • 扩容函数
  • remove函数
  • subList函数
  • 总结

本系列是Java详解,专栏地址:Java源码分析


ArrayList 官方文档:ArrayList (Java Platform SE 8 )
ArrayList .java源码共1760行,下载地址见我的文章:Java源码下载和阅读(JDK1.8/Java 11)

文件地址:openjdk-jdk11u-jdk-11.0.6-3/src/java.base/share/classes/java/util


在看源码前,先说一下为什么要看源码。因为我关注的公众号三天两头推送这种文章:《为什么阿里巴巴要求谨慎使用ArrayList中的subList方法》,这让我很恼火。于是今天就打算自己看源码。

注释

1.实现了List接口,并允许空元素。
2.提供了操作内部的数组的方法,类似Vector,但不是线程安全的。
3.size, isEmpty, get, set, iterator, listIterator 操作是O(1)时间复杂度的。add操作在时间均摊后是O(1)时间复杂度。
4.与LinkedList相比,constant factor常数因子较低。
5.每个ArrayList实例都有capacity,也就是在列表中存储元素的数组的大小。它总是至少与列表大小一样大。将元素添加到ArrayList后,其容量会自动增长。
6.可以使用ensureCapacity手动扩容,减少自动扩容带来的开销。
7.ArrayList不是synchronized,不是线程安全的。可以通过Collections.synchronizedList实现线程安全。
8.迭代器是fail-fast,如果在创建迭代器之后的任何时间对列表进行结构修改(除了通过迭代器自己的 方法remove或 add方法外),迭代器都将抛出异常ConcurrentModificationException

类的继承与实现

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

继承自AbstractList,实现了接口ListRandomAccess

构造函数

private static final int DEFAULT_CAPACITY = 10;//默认初始容量
private static final Object[] EMPTY_ELEMENTDATA = {};//空实例的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//使用无参构造初始化ArrayList时默认的空数组
transient Object[] elementData;//存储ArrayList元素的数组。
private int size;//the number of elements it contains

之所以存储数据的数组不设为private是为了让嵌套类访问更方便。

    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);}}public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}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;}}

add操作

add操作可以说是ArrayList最重要的操作之一了。

    public boolean add(E e) {modCount++;add(e, elementData, size);return true;}/*** This helper method split out from add(E) to keep method* bytecode size under 35 (the -XX:MaxInlineSize default value),* which helps when add(E) is called in a C1-compiled loop.*/private void add(E e, Object[] elementData, int s) {if (s == elementData.length)elementData = grow();elementData[s] = e;size = s + 1;}

这里有个问题,那就是代码明明很简单,为什么要写2个函数,不直接在一个函数中写完。根据注释,这是为了让add(E)函数的字节码的长度小于35,更有效的被调用于C1-compiled loop,这应该是JIT优化的手段。根据我查阅资料,函数的字节码小于35才有可能成为内联函数。

扩容函数

当数组满时,自动扩容:

    private Object[] grow() {return grow(size + 1);}private Object[] grow(int minCapacity) {return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity));}private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;private int newCapacity(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);if (newCapacity - minCapacity <= 0) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)return Math.max(DEFAULT_CAPACITY, minCapacity);if (minCapacity < 0) // overflowthrow new OutOfMemoryError();return minCapacity;}return (newCapacity - MAX_ARRAY_SIZE <= 0)? newCapacity: hugeCapacity(minCapacity);}

扩容函数表示一般扩容50%
一个很明显的缺点:使用数组的拷贝,并没有利用原来的数组。

remove函数

既然我们知道了ArrayList的底层实现就是数组,那么我很好奇ArrayList会如何实现删除操作:

    public E remove(int index) {Objects.checkIndex(index, size);final Object[] es = elementData;@SuppressWarnings("unchecked") E oldValue = (E) es[index];fastRemove(es, index);return oldValue;}private void fastRemove(Object[] es, int i) {modCount++;final int newSize;if ((newSize = size - 1) > i)System.arraycopy(es, i + 1, es, i, newSize - i);es[size = newSize] = null;}

根据注释,移除元素后,剩下的所有元素都要左移。使用系统提供的拷贝函数进行拷贝。

subList函数

到了关键的subList函数。

    public List<E> subList(int fromIndex, int toIndex) {subListRangeCheck(fromIndex, toIndex, size);return new SubList<>(this, fromIndex, toIndex);}private static class SubList<E> extends AbstractList<E> implements RandomAccess {private final ArrayList<E> root;private final SubList<E> parent;private final int offset;private int size;/*** Constructs a sublist of an arbitrary ArrayList.*/public SubList(ArrayList<E> root, int fromIndex, int toIndex) {this.root = root;this.parent = null;this.offset = fromIndex;this.size = toIndex - fromIndex;this.modCount = root.modCount;}public E set(int index, E element) {Objects.checkIndex(index, size);checkForComodification();E oldValue = root.elementData(offset + index);root.elementData[offset + index] = element;return oldValue;}public E get(int index) {Objects.checkIndex(index, size);checkForComodification();return root.elementData(offset + index);}}

看到返回一个新的对象,内部类的实例SubList
代码中的注释提到了要点:SubList函数提供原始list的一个视图
所以就有了以下注意事项:

1.subList函数返回的对象不能被转为ArrayList,只能当做List用。因为SubList只是ArrayList的内部类,他们之间并没有继承关系,故无法直接进行强制类型转换。
2.SubList并没有重新创建一个List,而是直接引用了原有的List(返回了父类的视图),只是指定了一下他要使用的元素的范围而已(从fromIndex(包含),到toIndex(不包含))。
3.当我们尝试通过set方法,改变subList中某个元素的值得时候,我们发现,原来的那个List中对应元素的值也发生了改变。
4.对父(sourceList)子(subList)List做的非结构性修改(non-structural changes),都会影响到彼此。
5.对子List做结构性修改,操作同样会反映到父List上。
6.对父List做结构性修改,子List会抛出异常ConcurrentModificationException。

总结

1.ArrayList 是一个数组队列,相当于 动态数组。
2.ArrayList中的操作不是线程安全的。所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
参考:

  • 原创 | 为什么阿里巴巴要求谨慎使用ArrayList中的subList方法
  • Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 - 如果天空不死 - 博客园

Java源码详解五:ArrayList源码分析--openjdk java 11源码相关推荐

  1. OkHttp3源码详解(五) okhttp连接池复用机制

    1.概述 提高网络性能优化,很重要的一点就是降低延迟和提升响应速度. 通常我们在浏览器中发起请求的时候header部分往往是这样的 keep-alive 就是浏览器和服务端之间保持长连接,这个连接是可 ...

  2. Rocksdb Compaction 源码详解(一):SST文件详细格式源码解析

    文章目录 前言 comapction流程概述 SST 文件细节 Footer meta index block filter meta block index meta block Compressi ...

  3. java多线程学习-java.util.concurrent详解(五) ScheduledThreadPoolExecutor

    转载于:http://janeky.iteye.com/blog/769965 我们先来学习一下JDK1.5 API中关于这个类的详细介绍: "可另行安排在给定的延迟后运行命令,或者定期执行 ...

  4. Rocksdb Compaction源码详解(二):Compaction 完整实现过程 概览

    文章目录 1. 摘要 2. Compaction 概述 3. 实现 3.1 Prepare keys 过程 3.1.1 compaction触发的条件 3.1.2 compaction 的文件筛选过程 ...

  5. 4.6 W 字总结!Java 11—Java 17特性详解

    作者 | 民工哥技术之路 来源 | https://mp.weixin.qq.com/s/SVleHYFQeePNT7q67UoL4Q Java 11 特性详解 基于嵌套的访问控制 与 Java 语言 ...

  6. java多线程学习-java.util.concurrent详解

    http://janeky.iteye.com/category/124727 java多线程学习-java.util.concurrent详解(一) Latch/Barrier 博客分类: java ...

  7. java异常体系结构详解

    java异常体系结构详解 参考文章: (1)java异常体系结构详解 (2)https://www.cnblogs.com/hainange/p/6334042.html 备忘一下.

  8. java异常处理机制详解

    java异常处理机制详解 参考文章: (1)java异常处理机制详解 (2)https://www.cnblogs.com/vaejava/articles/6668809.html 备忘一下.

  9. java nio详解,Java NIO API详解

    Java NIO API详解 在JDK 1.4以前,Java的IO操作集中在java.io这个包中,是基于流的阻塞(blocking)API.对于大多数应用来说,这样的API使用很方 便,然而,一些对 ...

最新文章

  1. 使用cpau.exe让不是管理员的用户也有权限运行哪些需要管理员权限的软件。
  2. 3D Computer Grapihcs Using OpenGL - 04 First Triangle
  3. Eclipse中怎样安装数据库建模工具ERMaster插件
  4. Mac 编译报错 symbol(s) not found for
  5. leetcode 463. 岛屿的周长(Java版)
  6. 了解JVM运行时的内存分配
  7. Linux如何从普通用户切换到root用户
  8. 【nodejs原理源码赏析(2)】KOA中间件的基本运作原理
  9. fckeditor异常总结---org.apache.commons.fileupload.FileUploadException
  10. According to the overall view of the patent
  11. qt mysql驱动不能用了,Qt使用msvc编译MySQL驱动_MySQL
  12. 顺无盘linux win10包,(2017.01.14)网维大师9.0.3.0无盘-xp-win7x32-x64-Win10x64公包
  13. 无线通信行业常用名词
  14. 北京邮电大学计算机院专业录取分数线,2017年北京邮电大学计算机科学与技术专业在北京录取分数线...
  15. 网络环路导致公司网络瘫痪问题排查
  16. 从一个程序员到月入7万自由职业者的故事—《打造你的赚钱机器》让我坐过了4站地铁...
  17. 微带贴片天线谐振边为什么是半波长?
  18. 查看电脑操作系统版本
  19. 视频特征提取常用范式总结
  20. Delphi 官方下载 地址

热门文章

  1. RDKit | 基于Ward方法对化合物进行分层聚类
  2. RDKit | 基于PCA探索化学空间
  3. 第四课.LinuxShell编程
  4. python 提交表单登录不成功_Python http requests模拟登录与提交表单的实现问题
  5. 获取pheatmap聚类后和标准化后的结果
  6. PNAS | 菌群大战:“单打独斗之殇”与“分而治之之利”
  7. 宏基因组理论教程4宏基因组物种组成
  8. R语言使用across函数一次性将多个数据列进行离散化(categorize):或者pivot_longer函数转化为长表、对转化为长表的数值数据列进行离散化、pivot_wider将数据转化为宽表
  9. R语言ggplot2可视化条形图(bar plot)、配置因子变量的全局填充色方案、这样不同数据集相同因子的填充色具有一致性(Fix colors to factor levels)
  10. JetBrains DataGrip工具配置数据库过程详解