本篇分析ArrayList的源码,在分析之前先跟大家谈一谈数组。数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集合类更好一些,这是使用数组的一大优势。但是我们知道数组存在致命的缺陷,就是在初始化时必须指定数组大小,并且在后续操作中不能再更改数组的大小。在实际情况中我们遇到更多的是一开始并不知道要存放多少元素,而是希望容器能够自动的扩展它自身的容量以便能够存放更多的元素。ArrayList就能够很好的满足这样的需求,它能够自动扩展大小以适应存储元素的不断增加。它的底层是基于数组实现的,因此它具有数组的一些特点,例如查找修改快而插入删除慢。本篇我们将深入源码看看它是怎样对数组进行封装的。首先看看它的成员变量和三个主要的构造器。

 //默认初始化容量
private static final int DEFAULT_CAPACITY = 10; //空对象数组
private static final Object[] EMPTY_ELEMENTDATA = {}; //对象数组
private transient Object[] elementData; //集合元素个数
private int size; //传入初始容量的构造方法
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0) {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
//新建指定容量的Object类型数组
this.elementData = new Object[initialCapacity];
} //不带参数的构造方法
public ArrayList() {
super();
//将空的数组实例传给elementData
this.elementData = EMPTY_ELEMENTDATA;
} //传入外部集合的构造方法
public ArrayList(Collection<? extends E> c) {
//持有传入集合的内部数组的引用
elementData = c.toArray();
//更新集合元素个数大小
size = elementData.length;
//判断引用的数组类型, 并将引用转换成Object数组引用
if (elementData.getClass() != Object[].class) {
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
}

可以看到ArrayList的内部存储结构就是一个Object类型的数组,因此它可以存放任意类型的元素。在构造ArrayList的时候,如果传入初始大小那么它将新建一个指定容量的Object数组,如果不设置初始大小那么它将不会分配内存空间而是使用空的对象数组,在实际要放入元素时再进行内存分配。下面再看看它的增删改查方法。

 //增(添加)
public boolean add(E e) {
//添加前先检查是否需要拓展数组, 此时数组长度最小为size+1
ensureCapacityInternal(size + 1);
//将元素添加到数组末尾
elementData[size++] = e;
return true;
} //增(插入)
public void add(int index, E element) {
//插入位置范围检查
rangeCheckForAdd(index);
//检查是否需要扩容
ensureCapacityInternal(size + 1);
//挪动插入位置后面的元素
System.arraycopy(elementData, index, elementData, index + 1, size - index);
//在要插入的位置赋上新值
elementData[index] = element;
size++;
} //删
public E remove(int index) {
//index不能大于size
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0) {
//将index后面的元素向前挪动一位
System.arraycopy(elementData, index+1, elementData, index, numMoved);
}
//置空引用
elementData[--size] = null;
return oldValue;
} //改
public E set(int index, E element) {
//index不能大于size
rangeCheck(index);
E oldValue = elementData(index);
//替换成新元素
elementData[index] = element;
return oldValue;
} //查
public E get(int index) {
//index不能大于size
rangeCheck(index);
//返回指定位置元素
return elementData(index);
}
每次添加一个元素到集合中都会先检查容量是否足够,否则就进行扩容,扩容的细节下面会讲到。我们先看具体增删改查要注意的地方。
增(添加):仅是将这个元素添加到末尾。操作快速。
增(插入):由于需要移动插入位置后面的元素,并且涉及数组的复制,所以操作较慢。
删:由于需要将删除位置后面的元素向前挪动,也会设计数组复制,所以操作较慢。
改:直接对指定位置元素进行修改,不涉及元素挪动和数组复制,操作快速。
查:直接返回指定下标的数组元素,操作快速。
通过源码看到,由于查找和修改直接定位到数组下标,不涉及元素挪动和数组复制所以较快,而插入删除由于要挪动元素,涉及到数组复制,操作较慢。并且每次添加操作还可能进行数组扩容,也会影响到性能。下面我们看看ArrayList是怎样动态扩容的。
 private void ensureCapacityInternal(int minCapacity) {
//如果此时还是空数组
if (elementData == EMPTY_ELEMENTDATA) {
//和默认容量比较, 取较大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//数组已经初始化过就执行这一步
ensureExplicitCapacity(minCapacity);
} private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果最小容量大于数组长度就扩增数组
if (minCapacity - elementData.length > 0) {
grow(minCapacity);
}
} //集合最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //增加数组长度
private void grow(int minCapacity) {
//获取数组原先的容量
int oldCapacity = elementData.length;
//新数组的容量, 在原来的基础上增加一半
int newCapacity = oldCapacity + (oldCapacity >> 1);
//检验新的容量是否小于最小容量
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
//检验新的容量是否超过最大数组容量
if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}
//拷贝原来的数组到新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
每次添加元素前会调用ensureCapacityInternal这个方法进行集合容量检查。在这个方法内部会检查当前集合的内部数组是否还是个空数组,如果是就新建默认大小为10的Object数组。如果不是则证明当前集合已经被初始化过,那么就调用ensureExplicitCapacity方法检查当前数组的容量是否满足这个最小所需容量,不满足的话就调用grow方法进行扩容。在grow方法内部可以看到,每次扩容都是增加原来数组长度的一半,扩容实际上是新建一个容量更大的数组,将原先数组的元素全部复制到新的数组上,然后再抛弃原先的数组转而使用新的数组。至此,我们对ArrayList中比较常用的方法做了分析,其中有些值得注意的要点:
1. ArrayList底层实现是基于数组的,因此对指定下标的查找和修改比较快,但是删除和插入操作比较慢。
2. 构造ArrayList时尽量指定容量,减少扩容时带来的数组复制操作,如果不知道大小可以赋值为默认容量10。
3. 每次添加元素之前会检查是否需要扩容,每次扩容都是增加原有容量的一半。
4. 每次对下标的操作都会进行安全性检查,如果出现数组越界就立即抛出异常。
5. ArrayList的所有方法都没有进行同步,因此它不是线程安全的。
6. 以上分析基于JDK1.7,其他版本会有些出入,因此不能一概而论。

Java集合系列[1]----ArrayList源码分析的更多相关文章

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

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

  2. java集合系列之LinkedList源码分析

    java集合系列之LinkedList源码分析 LinkedList数据结构简介 LinkedList底层是通过双端双向链表实现的,其基本数据结构如下,每一个节点类为Node对象,每个Node节点包含 ...

  3. Java集合系列[4]----LinkedHashMap源码分析

    这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...

  4. Java集合系列:-----------03ArrayList源码分析

    上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayLi ...

  5. Java集合系列[3]----HashMap源码分析

    前面我们已经分析了ArrayList和LinkedList这两个集合,我们知道ArrayList是基于数组实现的,LinkedList是基于链表实现的.它们各自有自己的优劣势,例如ArrayList在 ...

  6. Java集合系列[2]----LinkedList源码分析

    上篇我们分析了ArrayList的底层实现,知道了ArrayList底层是基于数组实现的,因此具有查找修改快而插入删除慢的特点.本篇介绍的LinkedList是List接口的另一种实现,它的底层是基于 ...

  7. java多线程系列(九)---ArrayBlockingQueue源码分析

    java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...

  8. Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式

    在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...

  9. Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式

    通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...

随机推荐

  1. gcc 源代码分析-前端篇3

    3. GCC怎样函表示一个函数   对c语言来说.函数是其核心,全部的东西都在环绕着函数在转.对于一个函数来说.它基本的一些特性例如以下:    1. 有一个返回值,在这里我们没有把返回值的函数觉得它 ...

  2. 003Java语言环境搭建

    JRE,JDK JRE(Java Runtime Environment java运行环境):包括java虚拟机和java程序所需要的核心类库, 如果要运行一个开发好的java程序,计算机中只需要安装 ...

  3. 【Java系列】Eclipse与Tomcat

    描述 本篇文章简要讲述Eclipse和Tomcat搭建,具体包括如下内容: (1)Eclipse安装和汉化 (2)Tomcat安装和CATALIAN_HOME变量配置 (3)在Eclipse中配置To ...

  4. 自学Zabbix3.6.1-触发器triggers创建

    1. 触发器概念 触发器(triggers)是什么?触发器使用逻辑表达式来评估通过item获取到得数据是处于哪种状态,item一收回数据,讲解任务交给触发器去评估状态,明白触发器是怎么一回事了把?在触 ...

  5. mongodb设置用户名和密码

    需求:我们需要在一个mongodb上面新建两个数据库,每个数据库的用户名和密码不一样,讲道理来说我们直接设置admin,就可以控制所有的数据库,不过用起来总是感觉有各种问题,目前还不太熟悉mongod ...

  6. jspsmart(保存文件)+poi(读取excel文件)操作excel文件

    写在前面: 项目环境:jdk1.4+weblogic 需求:能上传excel2003+2007 由于项目不仅需要上传excel2003,还要上传excel2007,故我们抛弃了jxl(只能上传exce ...

  7. Pycharm配置(二)

    1.主题 这部分教程主要介绍如何创建一个Python工程并使其具有Pycharm的代码风格.你将会看到Pycharm使你的源码变得非常简洁美观,带有合适的缩进.空格等等,因此Pycharm也是一款代码 ...

  8. JavaScript:AOP实现

    AOP的概念,使用过Spring的人应该都不陌生了.Dojo中,也是支持AOP的.对于JavaScript的其他框架.库不知道有没有AOP的支持.相信即便没有支持,也不会太远了.下面就介绍一下使用Ja ...

  9. ios 访问隐私信息 info.plist 中的字段

    1.iOS10相册相机闪退bug: iOS10系统下调用系统相册,相机功能,遇到闪退的情况,描述如下: This app has crashed because it attempted to acc ...

  10. DeepLearning.ai学习笔记(三)结构化机器学习项目--week2机器学习策略(2)

    一.进行误差分析 很多时候我们发现训练出来的模型有误差后,就会一股脑的想着法子去减少误差.想法固然好,但是有点headlong~ 这节视频中吴大大介绍了一个比较科学的方法,具体的看下面的例子 还是以猫 ...