ArrayList 是 List 集合的列表经典实现,其底层采用定长数组实现,可以根据集合大小进行自动扩容。

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

原理

为了深入理解 ArrayList 的原理,我们将从类成员变量、构造方法、核心方法两个方面逐一介绍。

类成员变量

// 默认初始化大小
private static final int DEFAULT_CAPACITY = 10;
// 空列表数据。初始化时如果没有指定大小,则将此值赋予elementData
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认空列表数据。如果没有指定大小,那么将此值赋予elementData
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 列表数据
transient Object[] elementData;
// 列表大小
private int size;

构造方法

ArrayList 一共有 3 个构造方法:

// 空构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 指定大小
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(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}

从第一个构造方法可以看到,如果没有指定大小,那么就将 elementData 赋值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA。而从第二个构造方法可以看到,如果指定了大小为 0,那么就将 elementData 赋值为 EMPTY_ELEMENTDATA。

核心方法

在 ArrayList 中最为核心的是获取、插入、删除、扩容这几个方法。

获取

获取的源码非常简单,只需对 index 做有效性校验。如果参数合法,那么直接返回对应数组下标的数据。

public E get(int index) {
rangeCheck(index);
return elementData(index);
}

插入

插入一共有两种实现方式,第一种是直接插入列表尾部,另一种是插入某个位置。

// 直接插入尾部
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
// 插入某个位置
public void add(int index, E element) {
rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}

如果是直接插入尾部的话,那么只需调用 ensureCapacityInternal 方法做容量检测。如果空间足够,那么就插入,空间不够就扩容后插入。

如果是插入的是某个位置,那么就需要将 index 之后的所有元素后移以为,之后再将元素插入至 index 处。

删除

ArrayList 的删除方法有两个,分别是:

  • 删除某个位置的元素:remove(int index)
  • 删除某个具体的元素:remove(Object o)

我们先来看第一个删除方法:删除某个位置的元素。

// 删除某个位置的元素
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);
elementData[--size] = null; // clear to let GC do its work return oldValue;
}

上述代码的逻辑大致是这样的:首先做参数范围检查,接着将 index 位置后的所有元素都往前挪一位,最后减少列表大小。

我们继续看第二个删除方法:删除某个特定的元素。

public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

上述代码的逻辑大致是:首先,遍历列表的所有元素,找到需要删除的元素索引,最后调用 fastRemove 方法删除该元素。我们继续看看 fastRemove 方法的实现。

/*
* 用私有的方法 fastRemove 方法跳过边界检查,不返回删除值。
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}

这里会有一个疑问,那就是为什么不直接复用 remove(int index) 方法,而要新写一个方法呢?答案在 fastRemove 方法的注释中已经写了,就是为了跳过边界检查,提高效率。

扩容

扩容是 ArrayList 的核心方法,当插入的时候容量不足,便会触发扩容。我们可以看到在插入的两个方法中都调用了扩容方法——ensureCapacityInternal。

private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

ensureCapacityInternal 方法直接调用 ensureExplicitCapacity 实现。

private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

ensureExplicitCapacity 方法首先判断容量是否足够,如果不够就调用 grow 方法扩容。

private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}

grow 方法的大致逻辑为:将原有列表容量扩大为原来的 1.5 倍。如果还是不够,那么直接扩大为最小容量(minCapacity)。

总结

经过上面的分析,我们可以知道 ArrayList 有如下特点:

  • 底层基于数组实现,读取速度快,修改速度慢(读取时间复杂度O(1),修改时间复杂度O(N))。
  • 非线程安全。
  • ArrayList 每次默认扩容为原来的 1.5 倍。

集合系列 List(二):ArrayList的更多相关文章

  1. Java 集合系列之二:List基本操作

    1. Java List 1. Java List重要观点 Java List接口是Java Collections Framework的成员. List允许您添加重复元素. List允许您拥有'nu ...

  2. Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例

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

  3. Java 集合系列(二)—— ArrayList

    ArrayList ArrayList 是通过一个数组来实现的,因此它是在连续的存储位置存放对象的引用,只不过它比 Array 更智能,能够根据集合长度进行自动扩容. 假设让我们来实现一个简单的能够自 ...

  4. 【Java集合系列一】ArrayList解析

    一.基础简介 1.ArrayList继承关系 2.底层用数组来存储数据,数据会在ArrayList创建的时候一并初始化.如果创建ArrayList的时候,没有设置容量,则会delay到第一次add数据 ...

  5. 【转】Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例

    原文网址:http://www.cnblogs.com/skywang12345/p/3308556.html 上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具 ...

  6. Java 集合系列03之 ArrayList详细介绍

    ArrayList做为List接口中最常用的实现类,必须掌握. 一.ArrayList简介 与Java中的数组相比ArrayList的容量可以动态增加.它继承与AbstractList,实现了List ...

  7. 深入理解JAVA集合系列四:ArrayList源码解读

    在开始本章内容之前,这里先简单介绍下List的相关内容. List的简单介绍 有序的collection,用户可以对列表中每个元素的插入位置进行精确的控制.用户可以根据元素的整数索引(在列表中的位置) ...

  8. Java 集合系列04之 fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)

    概要 前面,我们已经学习了ArrayList.接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解.内容包括::1 fail-fast简介2 fail-fast示例 ...

  9. Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)

    概要 前面,我们学完了List的全部内容(ArrayList, LinkedList, Vector, Stack). Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 Ja ...

  10. 【转】Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)

    概要 前面,我们学完了List的全部内容(ArrayList, LinkedList, Vector, Stack). Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 Ja ...

随机推荐

  1. 解决springmvc返回中文乱码问题

  2. [git] 基础命令笔记

    --内容整理自廖雪峰的GIT教程-- git status 查看当前工作区状态,显示未跟踪的文件以及未上传的修改记录 git init 使当前文件夹变成Git可以管理的仓库 git add xxx 将 ...

  3. 《ElasticSearch6.x实战教程》之实战ELK日志分析系统、多数据源同步

    第十章-实战:ELK日志分析系统 ElasticSearch.Logstash.Kibana简称ELK系统,主要用于日志的收集与分析. 一个完整的大型分布式系统,会有很多与业务不相关的系统,其中日志系 ...

  4. PHP使用array_unique对二维数组去重处理

    去重,点这里,东西是好东西,就是有点懒.莫见怪

  5. 史上最全IO流详解,看着一篇足矣

    一:要了解IO,首先了解File类 File类里面的部分常量,方法 No. 方法或常量 类型 描述 1 public static final String pathSeparator 常量 表示路径 ...

  6. nodejs 获取客户端 ip 地址

    应用场景: php:我们需要拿到用户客户端的ip信息,以识别用户位置,但现在我们拿到的地址永远是杭州 前端:我查一下,稍等 .... 明白了,我们加了一层 node 服务器,服务器在杭州,你们拿到的是 ...

  7. docker环境下使用gitlab,gitlab-runner 为 NetCore 持续集成

    环境 Centos7.6 安装应用docker,docker-compose (我的Centos是用Hyper-V跑的分了8G的内存,阿里云2G根本跑不起来gitlab) 为了保证我的Centos环境 ...

  8. Tips 14:思维导图读书笔记法

    Tips 14:思维导图读书笔记法作读书笔记不仅能提高阅读书.文的效率,而且能提高科学研究和写作能力.读书笔记一般分为摘录.提纲.批注.心得几种,这里特别推荐思维导图式的读书笔记. 通过思维导图先大概 ...

  9. 并查集_HDU 1232_畅通工程

    某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇.省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可). ...

  10. Linux系统命令。

      help:命令用于显示shell内部命令的帮助信息.help命令只能显示shell内部的命令              帮助信息.而对于外部命令的帮助信息只能使用man或者info命令查看   m ...