Java源码详解五:ArrayList源码分析--openjdk java 11源码
文章目录
- 注释
- 类的继承与实现
- 构造函数
- 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
,实现了接口List
,RandomAccess
。
构造函数
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源码相关推荐
- OkHttp3源码详解(五) okhttp连接池复用机制
1.概述 提高网络性能优化,很重要的一点就是降低延迟和提升响应速度. 通常我们在浏览器中发起请求的时候header部分往往是这样的 keep-alive 就是浏览器和服务端之间保持长连接,这个连接是可 ...
- Rocksdb Compaction 源码详解(一):SST文件详细格式源码解析
文章目录 前言 comapction流程概述 SST 文件细节 Footer meta index block filter meta block index meta block Compressi ...
- java多线程学习-java.util.concurrent详解(五) ScheduledThreadPoolExecutor
转载于:http://janeky.iteye.com/blog/769965 我们先来学习一下JDK1.5 API中关于这个类的详细介绍: "可另行安排在给定的延迟后运行命令,或者定期执行 ...
- Rocksdb Compaction源码详解(二):Compaction 完整实现过程 概览
文章目录 1. 摘要 2. Compaction 概述 3. 实现 3.1 Prepare keys 过程 3.1.1 compaction触发的条件 3.1.2 compaction 的文件筛选过程 ...
- 4.6 W 字总结!Java 11—Java 17特性详解
作者 | 民工哥技术之路 来源 | https://mp.weixin.qq.com/s/SVleHYFQeePNT7q67UoL4Q Java 11 特性详解 基于嵌套的访问控制 与 Java 语言 ...
- java多线程学习-java.util.concurrent详解
http://janeky.iteye.com/category/124727 java多线程学习-java.util.concurrent详解(一) Latch/Barrier 博客分类: java ...
- java异常体系结构详解
java异常体系结构详解 参考文章: (1)java异常体系结构详解 (2)https://www.cnblogs.com/hainange/p/6334042.html 备忘一下.
- java异常处理机制详解
java异常处理机制详解 参考文章: (1)java异常处理机制详解 (2)https://www.cnblogs.com/vaejava/articles/6668809.html 备忘一下.
- java nio详解,Java NIO API详解
Java NIO API详解 在JDK 1.4以前,Java的IO操作集中在java.io这个包中,是基于流的阻塞(blocking)API.对于大多数应用来说,这样的API使用很方 便,然而,一些对 ...
最新文章
- 使用cpau.exe让不是管理员的用户也有权限运行哪些需要管理员权限的软件。
- 3D Computer Grapihcs Using OpenGL - 04 First Triangle
- Eclipse中怎样安装数据库建模工具ERMaster插件
- Mac 编译报错 symbol(s) not found for
- leetcode 463. 岛屿的周长(Java版)
- 了解JVM运行时的内存分配
- Linux如何从普通用户切换到root用户
- 【nodejs原理源码赏析(2)】KOA中间件的基本运作原理
- fckeditor异常总结---org.apache.commons.fileupload.FileUploadException
- According to the overall view of the patent
- qt mysql驱动不能用了,Qt使用msvc编译MySQL驱动_MySQL
- 顺无盘linux win10包,(2017.01.14)网维大师9.0.3.0无盘-xp-win7x32-x64-Win10x64公包
- 无线通信行业常用名词
- 北京邮电大学计算机院专业录取分数线,2017年北京邮电大学计算机科学与技术专业在北京录取分数线...
- 网络环路导致公司网络瘫痪问题排查
- 从一个程序员到月入7万自由职业者的故事—《打造你的赚钱机器》让我坐过了4站地铁...
- 微带贴片天线谐振边为什么是半波长?
- 查看电脑操作系统版本
- 视频特征提取常用范式总结
- Delphi 官方下载 地址
热门文章
- RDKit | 基于Ward方法对化合物进行分层聚类
- RDKit | 基于PCA探索化学空间
- 第四课.LinuxShell编程
- python 提交表单登录不成功_Python http requests模拟登录与提交表单的实现问题
- 获取pheatmap聚类后和标准化后的结果
- PNAS | 菌群大战:“单打独斗之殇”与“分而治之之利”
- 宏基因组理论教程4宏基因组物种组成
- R语言使用across函数一次性将多个数据列进行离散化(categorize):或者pivot_longer函数转化为长表、对转化为长表的数值数据列进行离散化、pivot_wider将数据转化为宽表
- R语言ggplot2可视化条形图(bar plot)、配置因子变量的全局填充色方案、这样不同数据集相同因子的填充色具有一致性(Fix colors to factor levels)
- JetBrains DataGrip工具配置数据库过程详解