ArrayList类源码解析——ArrayList动态数组的实现细节(基于JDK8)
一、基本概念
ArrayList是一个可以添加对象元素,并进行元素的修改查询删除等操作的容器类。ArrayList底层是由数组实现的,所以和数组一样可以根据索引对容器对象所包含的元素进行快速随机的查询操作,其时间复杂度为O(1)。但是和数组不同的是,数组对象创建后数组长度是不变的,ArrayList对象创建后其长度是可变的,所以ArrayList也称为动态数组,那么ArrayList的动态数组数据结构又是如何实现的呢?接下来打开ArrayList源码看看。
二、源码分析
2.1、ArrayList类的继承与实现

图2.1 ArrayList类的继承与实现(不包括继承ArrayList的类)
- 从继承接口可以发现,ArrayList继承了AbstractCollection抽象类,实现了List接口,并且实现还实现了RandomAccess,Cloneable和Serializable三个标记接口,所以ArrayList的的对象具有能根据索引对元素进行快速随机访问、可以调用Object的clone()方法、可以进行序列化的特性。有关Java的标记接口是什么,在上一篇文章
《Java的四个标记接口Serializable、Cloneable、RandomAccess和Remote接口》 进行了总结
2.2、ArrayList的属性
- 通过源码可以知道ArrayList主要有以下属性

private static final long serialVersionUID = 8683452581122892189L;//用于ArrayList对象序列化的版本ID
private static final int DEFAULT_CAPACITY = 10;//ArrayList对象的默认容量大小
private static final Object[] EMPTY_ELEMENTDATA = {};//用于Null的ArrayList对象
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//用于创建一个默认容量大小的ArrayList对象
transient Object[] elementData; //用于创建动态大小的ArrayList对象,这个实例变量引用的数组可以说就是ArrayList容器
private int size;//ArrayList对象的尺寸

2.3、ArrayList类的三个构造函数
一个无参构造函数,一个传入一个int 量指定容量的构造函数和一个传入一个容器对象来进行初始化的构造函数

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初始容量的构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//一个默认容量的ArrayList无参构造器
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
//一个参数为一个容器对象的构造器,将该容器对象转化为一个数组对象后,将ArrayList对象内的数组引用elementData初始化为这个数组对象(Object[])

2.4、ArrayList动态数组的具体实现细节
ArrayList对象创建后,是怎么改变这个对象的容量实现动态数组的呢,来看看动态数组实现的几个重要方法
- grow方法。实现了ArrayList对象容量增加的方式,一般ArrayList新的容量为原容量的1.5倍大小,源码如下

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//int整型的最大值减8
private void grow(int minCapacity) { int oldCapacity = elementData.length;//原ArrayList对象的容量
int newCapacity = oldCapacity + (oldCapacity >> 1);//新的容量变为原容量+原容量/2,也就是新的容量增大为原来的1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//新的容量如果还是小于指定容量,则新容量的值更新为指定容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//如果新容量大于int整数的最大值减8的值,则调用hugeCapacity(minCapacity),新的容量为Interger.MAX_VALUE,即最大的Int整数
elementData = Arrays.copyOf(elementData, newCapacity);
//创建一个数组长度为newCapacity,包含所有原ArrayList内elementData所有元素的新的elementDate数组

- hugeCapacity方法,在grow方法中被调用

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

- trimToSize方法。将ArrayList对象内的elementData对象的长度变为size,size变量保存了ArrayList对象所含元素个数。使用这个方法可以使ArrayList对象内的elementData数组长度为元素个数,以避免不必要的内存占用。

public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)? EMPTY_ELEMENTDATA: Arrays.copyOf(elementData, size);
}
//当size小于elementData,如果size=0,令elementData为一个空数组;size>0,使elementDate数组变量指向包含原elememtData所有元素,且长度为元素个数size的新数组
}
- ensureExplicitCapacity方法,在参数minCapacity大于elementDate引用的数组的长度时,调用grow方法来增加容量
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
- ensureCapacity方法,如果构造ArrayList对象时调用的是无参构造器,此时elementDate数组还没创建,这个方法调用后可以确保ArrayList对象容量至少为10(默认值),且不小于minCapacity参数指定的值

public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}

- ensureCapacityInternal方法,在调用add方法增加元素时,调用这个方法来确保元素个数+1(size+1)小于或等于elementDate数组的长度,若size+1大于element.length,在ensureExplicitCapacity方法中会调用grow方法对ArrayList对象进行扩容

private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
/*Arraylist类调用无参构造器初始化后,elementData数组初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA(一个空对象数组Object{}),若elementDate为初始化的值这里返回的是默认容量和minCapacity中的较大值*/
}
return minCapacity;//若elementDate不是初始化的值,这个方法直接返回minCapacity的值
}

- add(int index)方法,实现了在指定的索引处添加元素,时间复杂度为O(n)

public void add(int index, E element) {
rangeCheckForAdd(index);//先检测索引是否越界
ensureCapacityInternal(size + 1); //确保ArrayList的容量,即elementData数组的长度,在添加元素前大于size+1
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//将从index处以及后面的元素的都向后移动一位,索引+1。所以调用这个方法的时间复杂度为O(n)
elementData[index] = element;
//将元素element添加到索引为index的位置
size++;//元素个数加一
}

- remove(int index)方法,实现了在指定的位置删除元素,时间复杂度为O(n)

public E remove(int index) {
rangeCheck(index);//检查索引是否越界
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;//这个元素后面的元素个数
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
//将这个元素后面的元素的前移一位,索引减一,原来index处的元素(要被删除的元素)被后面一位的元素覆盖。所以这个方法的时间复杂度为O(n)
elementData[--size] = null;
//数组索引为size处没有元素,使其为空,元素个数size-1
return oldValue;
//将删除的元素作为方法的返回值
}

(ArrayList还有很多其他的方法,以上是ArrayList实现动态数组的主要方法,ArrayList类其他的方法在这里就不一一赘述了)
ArrayList类源码解析——ArrayList动态数组的实现细节(基于JDK8)的更多相关文章
- Java集合---Array类源码解析
Java集合---Array类源码解析 ---转自:牛奶.不加糖 一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Prim ...
- java.lang.Void类源码解析_java - JAVA
文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 在一次源码查看ThreadGroup的时候,看到一段代码,为以下: /* * @throws NullPointerEx ...
- Java泛型底层源码解析-ArrayList,LinkedList,HashSet和HashMap
声明:以下源代码使用的都是基于JDK1.8_112版本 1. ArrayList源码解析 <1. 集合中存放的依然是对象的引用而不是对象本身,且无法放置原生数据类型,我们需要使用原生数据类型的包 ...
- (一)ArrayList集合源码解析
一.ArrayList的集合特点 问题 结 论 ArrayList是否允许空 允许 ArrayList是否允许重复数据 允许 ArrayList是否有序 有序 ArrayList是否线程安全 ...
- 源码解析 || ArrayList源码解析
前言 这篇文章的ArrayList源码是基于jdk1.8版本的源码,如果与前后版本的实现细节出现不一致的地方请自己多加注意.先上一个它的结构图 ArrayList作为一个集合工具,对于我而言它值得我们 ...
- ArrayList类源码浅析(一)
1.首先来看一下ArrayList类中的字段 可以看出,ArrayList维护了一个Object数组,默认容量是10,size记录数组的长度: 2.ArrayList提供了三个构造器:ArrayLis ...
- ArrayList LinkedList源码解析
在java中,集合这一数据结构应用广泛,应用最多的莫过于List接口下面的ArrayList和LinkedList; 我们先说List, public interface List<E> ...
- ArrayList类源码浅析(二)
1.removeAll(Collection<?> c)和retainAll(Collection<?> c)方法 第一个是从list中删除指定的匹配的集合元素,第二个方法是用 ...
- Java集合---Arrays类源码解析
一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类型: ...
随机推荐
- PHP百度AI的OCR图片文字识别
第一步可定要获取百度的三个东西 要到百度AI网站(http://ai.baidu.com/)去注册 然后获得 -const APP_ID = '请填写你的appid'; -const API_KEY ...
- Xshell连接Linux慢问题解决办法
由于各种原因,经常更换网络环境,然后发现,每次更换网络环境后,xshell连接虚拟机的rhel或者CentOS都几乎是龟速.... 今天专门查了一下解决方案: 原来是ssh的服务端在连接时会自动检测d ...
- Node.js基础学习三之登录功能
本篇介绍Node.js访问数据库并返回数据给客户端 需求基于Node.js学习(二) 数据库请下载:user.sql 1.创建user 实体类(model-user.js) function User ...
- Max Sum Plus Plus HDU - 1024
Max Sum Plus Plus HDU - 1024 Now I think you have got an AC in Ignatius.L's "Max Sum" ...
- windows系统开启虚拟化
电脑如何开启虚拟进化 当VMware安装系统时,如果电脑没有开启虚拟化,在安装过程中,会发生错误. 查看电脑是否开启虚拟化,[任务管理器] 正常情况如上图,而当虚拟化没有开启的时候,如何解决? int ...
- JSP页面静态包含和动态包含的区别与联系
---恢复内容开始--- JSP页面静态包含和动态包含的区别与联系: 1.<%@ include file=" " %> 是指令元素,<jsp:include p ...
- etcd 启动错误
Apr 26 16:17:25 ceph-0 etcd: f281dc69fb4dd3d8 became candidate at term 3574Apr 26 16:17:25 ceph-0 et ...
- C#线程同步(1)- 临界区&Lock
文章原始出处 http://xxinside.blogbus.com/logs/46441956.html 预备知识:线程的相关概念和知识,有多线程编码的初步经验. 一个机会,索性把线程同步的问题在C ...
- Bigger-Mai 养成计划,subprocess模块
subprocess模块是python从2.4版本开始引入的模块.主要用来取代 一些旧的模块方法,如os.system.os.spawn*.os.popen*.commands.*等.subproce ...
- xml转json和实体类的两种方式
本文为博主原创,未经允许不得转载: xml在http通信中具有较高的安全性和传输速度,所以应用比较广泛, 在项目中往往需要对xml,json和实体类进行相互转换,在这里总结一下自己所用到的一些方法: ...