ArrayList源码解析

基于jdk1.8

ArrayList的定义

类注释

  • 允许put null值,会自动扩容;
  • size isEmpty、get、set、add等方法时间复杂度是O(1);
  • 是非线程安全的,多线程情况下推荐使用CopyOnWriteArrayList或者Vector。
  • 增强for循环或使用迭代器过程中,如果数组大小被改变会抛出异常。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

从源码中我们可以看出,ArrayList继承了AbstractList,实现了List接口,RandomAccess,Cloneable,Serializable接口。

实现List接口提供了元素的添加、删除。修改。遍历等功能。
实现RandomAccess接口,他是一个标志性接口,表明实现这个接口后,List集合支持随机访问。
实现Cloneable接口表明ArrayList可以被克隆
实现Serializable接口表示ArrayList支持序列化,能被传输。

ArrayList是一个动态数组,与普通数组相比他的容量可以动态的扩容。

ArrayList的有关属性

 private static final long serialVersionUID = 8683452581122892189L;

    /*默认容量大小是10*/
private static final int DEFAULT_CAPACITY = 10; /**
* 空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {}; /**
* 用来共享空数组实例
* 我们把它和EMPTY_ELEMENTDATA数组区分出来,当添加第一个元素的时候知道需要扩增多少容量
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /**
* 保存ArrayList数据的数组
*/
transient Object[] elementData; // non-private to simplify nested class access /**
* 集合包含元素的个数
*/
private int size;

ArrayList的构造函数

public ArrayList(int initialCapacity) {
// 如果创建的时候传入的有值,并且大于0,就直接创建大小是传入的值的数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果创建的时候没有指定大小,就是一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//传入负数会抛出非法参数异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
} /**
* 构造一个空数组,当增加第一个元素的时候才确定数组容量是10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
} /** *构造函数的参数是一个集合,如果集合为null,会抛空指针异常
*
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
//如果集合的大小不为0 就通过Arrays.copy方法,把Object数组的元素一一复制到elementData数组中
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
// 注意这是java的一个bug,c.toArray()方法返回值可能不是Object数组,这里转成Object类型
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//如果集合里没有元素就是一个空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}

上面的那个// c.toArray might (incorrectly) not return Object[] (see 6260652)一般情况下都不会触发这个bug ,这里演示一下是怎么出现的。

public void testConstructor() {
List<String> arrayList = Arrays.asList("Hello");
//toArray方法返回Object数组类型
Object[] objects = arrayList.toArray();
log.info(objects.getClass().getSimpleName());
// 打印出来是 String[] //这样写是对的
//objects[0]="Java"; //这样就会报错因为数组元素的类型是String
objects[0] = new Object();
}

不过这个bug已经在Java9解决。

新增和扩容的实现

新增元素


/**
* Appends the specified element to the end of this list.
*增加元素到集合的末尾
*
*/
public boolean add(E e) {
//先扩容,使容量+1
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} /**
* 在指定位置增加元素
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//检查增加的位置是否越界
rangeCheckForAdd(index);
//扩容,size+1
ensureCapacityInternal(size + 1); // Increments modCount!!
//元素的复制,将 index之后的元素往后 移动一位,size++;
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//最后在index位置上赋值
elementData[index] = element;
size++;
}

扩容


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);
}
return minCapacity;
} private void ensureExplicitCapacity(int minCapacity) {
//记录数组被修改的次数加1
modCount++; // 如果我们期望的数组大小大于目前的数组长度,那么就扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} //扩容方法
private void grow(int minCapacity) {
// 先记录旧的数组大小
int oldCapacity = elementData.length;
//扩容后的容量=扩容前的+扩容前的大小/2,也就是说扩容后的值是原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果扩容后的值,小于期望的大小,那扩容后的值就改为期望值
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; //如果扩容后的值大于jvm所能分配的最大值,那么就用Integer.MAX_VALUE.,否则等于MAX_ARRAY_SIZE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 最后数组复制,底层是System.arraycopy()方法
elementData = Arrays.copyOf(elementData, newCapacity);
} private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
  • 扩容后数组的容量是原先数组容量的1.5倍。
  • ArrayList中的数组长度最大是Integer.MAX_VALUE,超过这个值,JVM就不会给数组分配内存空间了。
  • 新增时,并没有对新增元素进行严格校验,所以可以新增null。

其实我们可以看出,大多数方法中都用到了Arrays.copyOf方法

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
//新建数组并返回数组
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(
original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}

再看System.arraycopy

//src 是原数组,srcPos是原数组的位置,dest是目标数组,destPOS是目标数组的位置,length是拷贝的长度,这个方法是native方法,所以底层可能是C/C++编写的
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);

删除和迭代

ArrayList支持两种删除元素的方式

  1. remove(int index) 按照下标删除
  2. remove(Object o) 按照元素删除,删除第一个匹配到的元素。
public E remove(int index) {
//数组下标越界检查
rangeCheck(index);
//修改次数自增1
modCount++;
//记录被删除的值
E oldValue = elementData(index);
// 记录将要从index后移动的多少个位数到前面,因为size是从0开始,index是从1开始所以需要减一
int numMoved = size - index - 1;
if (numMoved > 0)
//从elementData的index+1处拷贝,拷贝到elementData数组中,从index处开始放置拷过来的元素,拷贝的长度是 numMoved
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // 最后一个元素赋值为null,帮助GC return oldValue;
} /**
根据值删除元素
*/
public boolean remove(Object o) {
//y因为ArrayList允许元素值为null,所以删除的时候遍历一次找到第一个是null的值,然后移除。
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++)
//这里是根据equals方法判断值是否相等,相等再根据索引删除
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
} /*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
//记录修改数加1
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; //最后元素赋值成null,有助于GC
} /**
移除集合中所有元素
*/
public void clear() {
modCount++; // clear to let GC do its work
for (int i = 0; i < size; i++)
//将集合中的每一个元素赋值为null
elementData[i] = null;
//集合元素的个数设置成0
size = 0;
}
  • 新增时因为没有对null做校验所以可以新增null,删除的时候就需要对null值做判断了,按照值删除元素实际上还是根据值找到这个元素的索引,进行删除。比较的方法是通过equals方法。
  • 某一个元素被删除后,ArrayList采用的是直接将这个元素后面的所有元素复制到原数组中,最后的一个元素的值设置成null,让JVM进行GC操作。

接下来看迭代


public ListIterator<E> listIterator(int index) {
//如果索引越界直接抛异常IndexOutOfBoundsException,否则返回从指定下标开始的所有元素列表(按熟顺序),ListIterator接口继承了Iterator接口
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
} /**
* 返回列表中的列表迭代器按顺序返回,迭代器是fail-fast
*/
public ListIterator<E> listIterator() {
return new ListItr(0);
} /**
* 以正确顺序返回元素列表的迭代器
*/
public Iterator<E> iterator() {
return new Itr();
} //ltr是ArrayList的内部类,实现了 Iterator接口,这里只是Itr的部分代码
private class Itr implements Iterator<E> {
int cursor; // i迭代过程中,下一个元素的位置,默认从0开始
int lastRet = -1; // 新增时表示上次迭代过程中,索引的位置;删除场景下是-1
int expectedModCount = modCount;
//期望修改的版本号=实际的修改版本号
Itr() {} public boolean hasNext() {
//判断下一个元素存不存在,只需知道下一个索引的下标是不是数组大小,如果不是返回true,如果是返回false
return cursor != size;
} @SuppressWarnings("unchecked")
public E next() {
//检查版本号是不是已经被修改
checkForComodification();
int i = cursor; //下一个元素的下标
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
//下一次迭代时元素的位置
cursor = i + 1;
return (E) elementData[lastRet = i];
} public void remove() {
//当lastRet小于0,也就是说在多个线程操作时,直接将迭代器中的列表删完了,里面没有元素了,就会抛出IllegalStateException
if (lastRet < 0)
throw new IllegalStateException();
//迭代过程中判断版本号有没有被修改,如果被修改抛出ConcurrentModificationException
checkForComodification(); try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
//lastRet=-1防止重复删除
lastRet = -1;
//下次迭代时期望的版本号和实际的版本号一致
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//版本号比较
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

小结

  • ArrayList底层是使用类似于动态数组实现的,默认构造方法初始化的容量是10,但是在没有指定容量大小时区add第一个元素时才会确定出数组的容量。
  • 扩容时,扩容后的长度是扩容前的1.5倍
  • 实现了RandomAccess接口,支持随机读写,get读取元素的性能较好
  • 线程时不安全的,是因为自身的elementData、size、modCount在进行各种操作时,都没有加锁
  • 新增(这里的新增默认是增加到尾部)和删除方法 的时间复杂度是O(1)

以上理解有误的地方欢迎指出,共同进步!

ArrayList源码解析--值得深读的更多相关文章

  1. 面试必备:ArrayList源码解析(JDK8)

    面试必备:ArrayList源码解析(JDK8) https://blog.csdn.net/zxt0601/article/details/77281231 概述很久没有写博客了,准确的说17年以来 ...

  2. ArrayList源码解析

    ArrayList简介 ArrayList定义 1 public class ArrayList<E> extends AbstractList<E> implements L ...

  3. 顺序线性表 ---- ArrayList 源码解析及实现原理分析

    原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7738888.html ------------------------------------ ...

  4. ArrayList源码解析(二)

    欢迎转载,转载烦请注明出处,谢谢. https://www.cnblogs.com/sx-wuyj/p/11177257.html 自己学习ArrayList源码的一些心得记录. 继续上一篇,Arra ...

  5. Java中的容器(集合)之ArrayList源码解析

    1.ArrayList源码解析 源码解析: 如下源码来自JDK8(如需查看ArrayList扩容源码解析请跳转至<Java中的容器(集合)>第十条):. package java.util ...

  6. Collection集合重难点梳理,增强for注意事项和三种遍历的应用场景,栈和队列特点,数组和链表特点,ArrayList源码解析, LinkedList-源码解析

    重难点梳理 使用到的新单词: 1.collection[kəˈlekʃn] 聚集 2.empty[ˈempti] 空的 3.clear[klɪə(r)] 清除 4.iterator 迭代器 学习目标: ...

  7. 【源码解析】- ArrayList源码解析,绝对详细

    ArrayList源码解析 简介 ArrayList是Java集合框架中非常常用的一种数据结构.继承自AbstractList,实现了List接口.底层基于数组来实现动态容量大小的控制,允许null值 ...

  8. ArrayList源码解析(一)

    源码解析系列主要对Java的源码进行详细的说明,由于水平有限,难免出现错误或描述不准确的地方,还请大家指出. 1.位置 ArrayList位于java.util包中. package java.uti ...

  9. Java集合-ArrayList源码解析-JDK1.8

    ◆ ArrayList简介 ◆ ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAcc ...

随机推荐

  1. P5857 「SWTR-03」Matrix

    原本自己有一个思路的,推了半天不太确定看了下题解,发现到后面完全不知道他代码在写些什么(我太弱了),所以打算自己理一下. 题解 首先我们可以肯定的一点就是,我们可以发现,一个矩阵的形态只和他横着和竖着 ...

  2. 详解Java中的IO输入输出流!

    目录 本片要点 基本分类 发展史 文件字符流 输出的基本结构 流中的异常处理 异常处理新方式 读取的基本结构 运用输入与输出完成复制效果 文件字节流 缓冲流 字符缓冲流 装饰设计模式 转换流(适配器) ...

  3. css 03-CSS样式表和选择器

    03-CSS样式表和选择器 #本文主要内容 CSS概述 CSS和HTML结合的三种方式:行内样式表.内嵌样式表.外部样式表 CSS四种基本选择器:标签选择器.类选择器.ID选择器.通用选择器 CSS几 ...

  4. 谷歌学术: but your computer or network may be sending automated queries. To protect our users, we can't process your request right now. See Google Help for more information.

    原因是屏蔽了日本和新加坡的服务器,切换服务器为其他地方即可

  5. 题解洛谷P1538【迎春舞会之数字舞蹈】

    方法:暴力,判断,输出 本题为了更好理解建议各位可以复制样例来研究,甚至可以复制题解来测试思想,相信大家不会抄. 有什么不好的请大佬们在评论里指出,谢谢 #include <bits/stdc+ ...

  6. JYadmin-react-antd react+antd封装的优秀后台模板集成方案("^1.0.0")

    版本:[ "JYadmin-react-antd": "^1.0.0"] 版权所有:微信公众号[微新悦] 原文链接:https://www.weixinyue. ...

  7. SpringBoot进阶教程(六十七)RateLimiter限流

    在上一篇文章nginx限流配置中,我们介绍了如何使用nginx限流,这篇文章介绍另外一种限流方式---RateLimiter. v限流背景 在早期的计算机领域,限流技术(time limiting)被 ...

  8. 简丽Framework-开篇

    简丽Framework-开篇 ​ 简丽Framework 是一个开源java Web开发框架. ​ 开源的框架.库.组件等比比皆是,每个开源产品都有它的定位和价值. ​ 简丽Framework的定位是 ...

  9. 解决No Python interpreter for the object

    1. File --> Settings 2. Project:[项目名] --> Project Interpreter --> 点击齿轮 3. 点击齿轮出现Add,点击Add 4 ...

  10. 加班申请单flowable中

    /* * Copyright (c) 2018-2028, Chill Zhuang All rights reserved. * * Redistribution and use in source ...