ArrayList源码分析笔记

先贴出ArrayList一些属性

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
/**
* 系列化ID.
*/
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认初始化容量.
*/
private static final int DEFAULT_CAPACITY = 10; /**
* 用于空实例的共享空数组实例.
*/
private static final Object[] EMPTY_ELEMENTDATA = {}; /**
* 共享的空数组实例,用于默认大小的空实例。我们将此与EMPTY_ELEMENTDATA区别开来,
以了解添加第一个元素时需要膨胀多少。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /**
* 存储ArrayList的元素的数组缓冲区。 ArrayList的容量是此数组缓冲区的长度。添加第
一个元素时,任何具有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空
ArrayList都将扩展为DEFAULT_CAPACITY。
*/
transient Object[] elementData; /**
* ArrayList的大小(它包含的元素数).
*
* @serial
*/
private int size;

以上属性注释都已经被翻译成中文,通过这些注释,我们大概能了解到的一些有价值的信息

  • ArrayList底层数据结构是一个Object数组

  • ArrayList的默认初始化容量为10

  • 一个空的ArrayList集合在添加第一个元素时被扩容为10

  • ArrayList的大小是通过一个名为size的int变量存储的

源码继续往下看,先看ArrayList的构造函数

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的无参构造函数时,这个ArrayList的Object数组被初始化为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA=10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
} /**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
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;
}
}

可以看到ArrayList一共有三个构造函数,一个无参构造,两个有参构造。

当我们在创建一个Arraylist集合时,如果不指定容量,也就是调用无参构造函数,那么这个Arraylist会被初始化为10.

public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

另外注意到另外两个有参构造,一个参数为int initialCapacity,也就是我们指定的初始化容量,这里对这个initialCapacity进行了一些简单的验证

public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//initialCapacity > 0
this.elementData = new Object[initialCapacity];//将一个容量为initialCapacity的新Object数组赋给elementData。
} else if (initialCapacity == 0) {//initialCapacity==0
this.elementData = EMPTY_ELEMENTDATA;//将EMPTY_ELEMENTDATA这个空object赋给elementData
} else {//其他情况,也就是小于0,直接抛异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}

另一个有参构造

    public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//将Collection集合转化为数组,然后赋给elementData这个object数组
if ((size = elementData.length) != 0) {//如果这个集合长度不为0
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)//如果集合转化后的数组不是Object数组
elementData = Arrays.copyOf(elementData, size, Object[].class);//转化为Object数组
} else {//其他情况,也就是elementData的长度为0嘛
// 替换为EMPTY_ELEMENTDATA空的数组
this.elementData = EMPTY_ELEMENTDATA;
}
}

可以看到这个构造函数,它是直接把一个Collection丢了进去,这也就是说,在new一个ArrayList时我们可以把一个Collection集合放到参数列表中。

接下来再来看ArrayList的add方法

 /**
* 将元素添加到列表的末尾
*
* @param e 要被追加到list中的元素
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;//将元素e添加到数组中,下标为size++,其实也就是size,然后添加后size+1,
return true;
}

这里就两行代码,调用ensureCapacityInternal这个方法,并且参数为size+1。点进去看下ensureCapacityInternal()这个方法

/*
通过这个方法我们可以看出,当我们第一次调用add方法的时候,elementData数组的size为0,那么size+1就为1,所以minCapacity也为1,这里先通过一个if判断,判断minCapacity是不是一个空的object数组,如果是的话,minCapacity就取DEFAULT_CAPACITY和minCapacity的最大值,也就是10嘛
*/
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//判断完之后进入了这个方法,同时参数为minCapacity,如果是第一次调用add方法的话参数为minCapacity就是默认大小10,不是第一次调用的话,就是size+1,
ensureExplicitCapacity(minCapacity);
} private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//这个grow()就是扩容函数,现在minCapacity的话,还是一样的分析,如果第一次调用add方法的话就是10,同时elementData.length为0,不是第一次调用的话minCapacity就是size+1,elementData.length也就是数组的长度为size
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

读完这一段源码,可以得出以下结论

  • 当我们创建ArrayList时,如果不指定ArrayList的大小,那么第一次调用add方法时,底层会调用扩容方法grow()对Arraylist进行扩容
  • 当ArrayList里面已经存满了元素的时候,再调用add方法,此时会底层会调用grow方法进行扩容,比如ArrayList的大小为10,里面也已经有10个元素了,当我们再调用add方法向里面添加元素的时候,此时Arraylist会触发扩容。

接下来我们再来看下grow方法,了解一些Arraylist到底是怎么样扩容的

private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果数组当前长度+数组当前长度/2 < 数组当前元素个数+1
if (newCapacity - minCapacity < 0)
//就把数组当前元素个数+1赋给newCapacity
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 最后我们可以看到,这里进行了一个复制操作,将elementData里面的元素,复制到一个新的长度为newCapacity的Object数组,然后再把它赋给elementData
elementData = Arrays.copyOf(elementData, newCapacity);
}

这段代码应该很清晰吧,grow方法传进来的参数就是上面minCapacity,第一次调用add方法时是10,其他时候调用时是size+1(当前存储元素个数+1)

这里简单来说就是将elementData当前长度给oldCapacity,然后newCapacity = oldCapacity + oldCapacity >> 1(左移操作,相当于除于2),如果oldCapacity = 10的话,newCapacity = 10 +(10/2),也就是15。通过以上代码,不难看出,ArrayList的扩容方法实际上就是将底层Object数组复制到了一个新的长度更长的数组里面去,而这个新数组的长度是原来的数组长度+原来数组长度的二分之一,其实就可以说了扩容了1.5倍。

通过对以上源码进行分析,我们可以得出以下结论了

总结:

  • ArrayList底层数据数据结构是一个Object类型的数组
  • ArrayList的默认初始化容量为10
  • 当我们在创建ArrsyList时不指定ArrsyList初始大小,第一次调用add方法时扩容为初始化大小10
  • ArrayList的大小是通过一个名为size的int变量存储的
  • ArrayList有一个构造函数允许Collection集合作为参数,并且会将这个Collection集合转化为数组
  • ArrayList在添加第elementData.length+1个元素时会发生扩容,比如数组长度为10,在添加第11个元素时扩容
  • ArrayList扩容大小为原来的1.5倍,底层实现是通过原来数组长度+原来数组长度左移1位完成,而不是直接通过乘法

ArrayList源码分析笔记的更多相关文章

  1. ArrayList源码分析笔记(jdk1.8)

    1.特点: ArrayList 是一个动态数组,它是线程不安全的,允许元素为null 可重复,插入有序 读写快,增删慢 扩容:默认容量 10,默认扩容1.5倍 建议指定容量大小,减少扩容带来的性能消耗 ...

  2. zeromq源码分析笔记之线程间收发命令(2)

    在zeromq源码分析笔记之架构说到了zmq的整体架构,可以看到线程间通信包括两类,一类是用于收发命令,告知对象该调用什么方法去做什么事情,命令的结构由command_t结构体确定:另一类是socke ...

  3. Java集合干货——ArrayList源码分析

    ArrayList源码分析 前言 在之前的文章中我们提到过ArrayList,ArrayList可以说是每一个学java的人使用最多最熟练的集合了,但是知其然不知其所以然.关于ArrayList的具体 ...

  4. ArrayList 源码分析

    ArrayList 源码分析 1. 结构   首先我们需要对 ArrayList 有一个大致的了解就从结构来看看吧. 1. 继承   该类继承自 AbstractList 这个比较好说 2. 实现 这 ...

  5. ArrayList源码分析超详细

    ArrayList源码分析超详解 想要分析下源码是件好事,但是如何去进行分析呢?以我的例子来说,我进行源码分析的过程如下几步: 找到类:利用 IDEA 找到所需要分析的类(ztrl+N查找ArraLi ...

  6. Java - ArrayList源码分析

    java提高篇(二一)-----ArrayList 一.ArrayList概述 ArrayList是实现List接口的动态数组,所谓动态就是它的大小是可变的.实现了所有可选列表操作,并允许包括 nul ...

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

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

  8. ArrayList源码分析超详细(转载)

    ArrayList源码分析超详细   ArrayList源码分析超详解 想要分析下源码是件好事,但是如何去进行分析呢?以我的例子来说,我进行源码分析的过程如下几步: 找到类:利用 IDEA 找到所需要 ...

  9. ArrayList源码分析--jdk1.8

    ArrayList概述   1. ArrayList是可以动态扩容和动态删除冗余容量的索引序列,基于数组实现的集合.  2. ArrayList支持随机访问.克隆.序列化,元素有序且可以重复.  3. ...

随机推荐

  1. hdu2157 How many ways??

    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission ...

  2. java——类、对象、private、this关键字

    一.定义  二.类的使用 实例:定义的类要在一个class文件内,实例化类的对象要在另一个文件内 类文件: 实例文件: 对象内存图: 先主函数入栈,之后新开一个对象存入堆内存中,之后调用的call方法 ...

  3. 仿ATM程序软件

    一.目标: ATM仿真软件 1 系统的基本功能 ATM的管理系统其基本功能如下:密码验证机制:吞锁卡机制:存取款功能:账户查询功能:转账功能等. 要求 要能提供以下几个基本功能: (1)系统内的相关信 ...

  4. Codeforces Round #643 (Div. 2) E. Restorer Distance (贪心,三分)

    题意:给你\(n\)个数,每次可以使某个数++,--,或使某个数--另一个++,分别消耗\(a,r,m\).求使所有数相同最少的消耗. 题解:因为答案不是单调的,所以不能二分,但不难发现,答案只有一个 ...

  5. AtCoder Beginner Contest 179 E - Sequence Sum (模拟)

    题意:\(f(x,m)\)表示\(x\ mod\ m\),\(A_{1}=1\),而\(A_{n+1}=f(A^{2}_{n},M)\),求\(\sum^{n}_{i=1}A_{i}\). 题解:多算 ...

  6. codefroces 7C

    C. Line time limit per test 1 second memory limit per test 256 megabytes input standard input output ...

  7. jupyter-notebook kernel died

    问题 在floydhub上跑个github上面的项目, 开了notebook模式运行, 一运行一会儿就kernel died了... 解决 我这儿的问题, 后来发现是出在: 在notebook中, 对 ...

  8. Battery API All In One

    Battery API All In One https://caniuse.com/?search=Battery navigator.getBattery() /* Promise {<pe ...

  9. How to enable HTTPS for local development in macOS using Chrome

    How to enable HTTPS for local development in macOS using Chrome HTTPS, macOS, Chrome local HTTPS htt ...

  10. EventBus / Event Bus

    EventBus / Event Bus EventEmitter / Event Emitter https://greenrobot.org/eventbus/documentation/ htt ...