一:idea可以自动生成UML类图,ctrl+alt+u
ArrayList类图



我没们看下类的继承关系
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

继承AbstracList抽象父类

然后分别实现:List接口(规定一些方法)、RandomAccess(可随机访问)、Cloneable(克隆接口)、Serializable(可序列化接口)

二:接着我们看主要的类成员属性
1.版本号
private static final long serialVersionUID = 8683452581122892189L;

2.容量为10的常量
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;

3.空数组实例(List list = new ArrayList(0) 如果带参初始化,但是参数为0,那么数组初始为该空数组)
/**
* Shared empty array instance used for empty instances.
*
*/
private static final Object[] EMPTY_ELEMENTDATA = {};

4.用于默认大小的空实例的共享空数组实例。我们将其与EMPTY_ELEMENTDATA区分开来,以了解在添加第一个元素时要膨胀多少。( List list = new ArrayList() 不传参这种初始化方式,数组会初始为该默认的空数组,与上面区分开来)
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

5.元素数组(从这里我们其实就能知道ArrayList底层是以数组为数据结构进行操作的)
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access

6.记录数组下标用的,默认为0,每次往数组里add一个元素,size++
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;


三:看下ArrayList初始化,ArrayList是通过构造方法进行初始化的。有三种不同的初始化方法,对应三种不同的构造方法。
1.事先传进来一个ArrayList初始化长度。
也就是说我们可以通过 List list = new ArrayList(20)  来构造一个具有指定初始容量的空列表
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//如果传进来的长度大于0,则直接初始化Object[]传进来的长度数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果长度为0则让数组等于Object[] EMPTY_ELEMENTDATA = {};空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//抛错
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}


2. List list = new ArrayList() 不传参(ps:jdk1.8版本现在初始化是为空数组了!!)
当未指定初始化大小时,会给elementData赋值为默认的空集合。
/**
* 不传参,初始化一个空数组,与上面空数组不为同一个
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

3.传入的参数为集合时,先将集合转为数组再赋值给elementData数组
/**
* 当传递的参数为集合类型时,会把集合类型转化为数组类型,并赋值给elementData
*/
public ArrayList(Collection<? extends E> c) {
//先转为数组
elementData = c.toArray();
//参数长度不为0
if ((size = elementData.length) != 0) {
//如果没有成功转为Object型数组,Object[].class得到的是class [Ljava.lang.Object;
if (elementData.getClass() != Object[].class)
//那么就进行拷贝
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}

四:添加方法 add()
1.这里我们首先假设是以List list = new ArrayList() 不传参的方式来初始化的ArrayList
然后我们第一次执行添加方法 list.add() 
注意这里的假设是下面所有分析的前提!
/**
* 将指定的元素添加到此列表的尾部。
*/
public boolean add(E e) {
//确保数组有合适的大小
ensureCapacityInternal(size + 1); // Increments modCount!!
//放入对应的数组下表
elementData[size++] = e;
return true;
}

分析:在执行添加的时候,会先调用ensureCapacityInternal()方法来确保当前的数组有合适的大小来添加元素

ensureCapacityInternal(size + 1)因为是第一次调用,并且初始化方法为List list = new ArrayList(),所以传入的参数为1
private void ensureCapacityInternal(int minCapacity) {
//如果elementData数组等于默认空元素数组则进入下面的逻辑(ps:我们假设是不传参初始化的ArrayList,所以会进入下面括号内的逻辑)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//DEFAULT_CAPACITY=10,比较两者数的大小,较大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

//将参数继续传入该函数
ensureExplicitCapacity(minCapacity);
}

分析:这里minCapacity传入的时候为1,经过比较取大值后,minCapacity为10。

这里我们可能会联想到,如果以List list = new ArrayList()方式初始化的空数组,在我们第一次进行add添加时,空数组需要进行扩容,那么扩容是不是一下扩容了10个长度大小?我们继续往下分析


private void ensureExplicitCapacity(int minCapacity) {
//记录修改次数加1
modCount++;
//当前传入minCapacity长度大于当前数组长度
if (minCapacity - elementData.length > 0)
//将参数传入该函数
grow(minCapacity);
}

分析:modCount++,然后执行grow()方法



/**
* 真正执行数组扩容的方法,先判断扩容的长度,最后执行Arrays.copyOf扩容
*/
private void grow(int minCapacity) {
//当前旧的数组长度
int oldCapacity = elementData.length;
//新数组长度等于 = 旧数组长度 + 旧数组长度除以2
int newCapacity = oldCapacity + (oldCapacity >> 1);
//小于传进来的参数则等于传进来的参数
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//大于int的最大长度(2^31 - 1) 再减去8长度,传入minCapacity参数,执行hugeCapacity()方法,指定新容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//执行Arrays.copyOf将旧数组扩容成新数组长度为newCapacity
elementData = Arrays.copyOf(elementData, newCapacity);
}

分析:在我们假设的前提下,minCapacity当前等于10,当前oldCapacity为0。

所以,经过逻辑判断后,newCapcity等于10。最后执行Arrays.copyOf(elementData, newCapacity)
将空数组,扩容成长度为10的新数组! 
这我们也就搞明白了,如果我们初始化ArrayList不传参,那么第一次添加元素时,数组会先扩容10个长度。
注意我们这里只是明确搞懂了初始化方式为不传参,第一次添加元素时,数组扩容机制。

接下来,根据前面的判断,我们很容易分析到如果当前list的元素小于10个,那么数组是不进行扩容的。
(你想想写这玩意的那帮家伙,肯定是这么干的,数组只有满了,才会去扩容,不然就是傻子。。。)
我们可以分析下第二次添加元素的情景:
/**
* 将指定的元素添加到此列表的尾部。
*/
public boolean add(E e) {
//确保数组有合适的大小
ensureCapacityInternal(size + 1); // Increments modCount!!
//
elementData[size++] = e;
return true;
}

分析:这里现在ensureCapacityInternal()传入的参数为2


private void ensureCapacityInternal(int minCapacity) {
//如果elementData数组等于默认空元素数组则进入下面的逻辑(ps:我们前面假设是不传参初始化的ArrayList,所以会进入下面括号内的逻辑)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//DEFAULT_CAPACITY=10,比较两者数的大小,较大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

//将参数等于10继续传入该函数
ensureExplicitCapacity(minCapacity);
}

分析:第二次添加,那么minCapacity就等于2。也就是minCapacity可以理解为当前元素个数。

现在数组已经不是默认的空数组,if条件不满足,所以直接走下面ensureExplicitCapacity()方法

private void ensureExplicitCapacity(int minCapacity) {
//记录修改次数加1
modCount++;
//当前传入minCapacity长度大于当前数组长度
if (minCapacity - elementData.length > 0)
//将参数传入该函数
grow(minCapacity);
}

分析:到这里就是记录修改次数加1了,2小于当前数组长度10,不满足,不往下执行。

所以,我们知道,当数组元素没有满时,或者更准确的说没有达到第二次条件时,它是不扩容的。

然后,我们继续考虑,那么什么时候进行第二次扩容,第二次扩容的大小又是多少?
private void ensureExplicitCapacity(int minCapacity) {
//记录修改次数加1
modCount++;
//当前传入minCapacity长度大于当前数组长度
if (minCapacity - elementData.length > 0)
//将参数传入该函数
grow(minCapacity);
}

分析:我们从这里很容易就看出,第二次准备扩容时,elementData.length当前的数组长度是10。当前的元素个数minCapacity为11个时,放不下,也就是数组个数已经满了,开始扩容。

传入参数11,进入grow()方法。

/**
* 真正执行数组扩容的方法,先判断扩容的长度,最后执行Arrays.copyOf扩容
*/
private void grow(int minCapacity) {
//当前旧的数组长度
int oldCapacity = elementData.length;
//新数组长度等于 = 旧数组长度 + 旧数组长度除以2 (也就是1.5倍)
int newCapacity = oldCapacity + (oldCapacity >> 1);
//小于传进来的参数则等于传进来的参数
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//大于int的最大长度(2^31 - 1)再减去8长度,传入minCapacity参数,执行hugeCapacity()方法
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//执行Arrays.copyOf将旧数组扩容成新数组长度为newCapacity
elementData = Arrays.copyOf(elementData, newCapacity);
}

分析:此时,newCapactiy值等于旧数组的1.5倍,然后进行复制扩容。也就是说,第二次包括以后数组满了再扩容,在满足没有超过MAX_ARRAY_SIZE的前提下每次数组扩容都是扩容为原来数组长度的1.5倍长。


至于如果当前扩容的长度大于了MAX_ARRAY_SIZE,执行hugeCapacity()方法,不按照1.5倍扩容机制,而是重新计算扩容长度。很简单,我们进去看下就懂了。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
分析:这里的minCapacity代表的是需要扩容的长度,如果需要扩容长度大于了int最大值减去8
那么,扩容的长度直接等于int最大值,也就是2^31-1。不大于,则直接等于2^31-1-8。


2.假设是以List list = new ArrayList(5) 传参的方式来初始化的ArrayList,初始化长度为5
还是按照上面代码去理思路,很容易就可以分析,在添加的元素不大于5时,不会去扩容。
//当前传入minCapacity长度大于当前数组长度
if (minCapacity - elementData.length > 0)
//将参数传入该函数
grow(minCapacity);

同理,接下来每次添加元素,如果当前数组元素已经填满,那么就需要去扩容。扩容扔然是按1.5倍去扩容,如果当前数组乘以1.5倍后长度已经大于MAX_ARRY_SIZE,那么扩容仍然会走hugeCapacity()策略,同分析1。

总结:我们已经知道了ArrayList的扩容机制,简单来说就是在满足扩容新数组长度不大于MAX_ARRY_SIZE长度下:
不传参初始化ArrayList()第一次扩容长度为10,接下来每次都是按1.5倍扩容。
传参初始化,每次扩容都是按1.5倍来扩的。

这里提个小建议:就是如果我们知道了集合的元素多少,那么尽量用传参的方式初始化ArrayList,这样我们就可以避免因为数组容量不够而去进行扩容操作,每次扩容其实都是比较耗性能的。

五:remove()方法
/**
* 通过移动数组后面所有的元素覆盖当前索引元素,从而达到删除当前元素的效果
* 数组最后一个索引值置为null,说明ArrayList可以查找null值
*/
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);
//数组最后一个索引值置为null,并且索引减1
elementData[--size] = null; // clear to let GC do its work

return oldValue;
}

六:set(int index, E element)
/**
* 因为底层是数组,所以set时,可以直接根据索引进行覆盖旧的值
*/
public E set(int index, E element) {
//检查索引是否越界
rangeCheck(index);

E oldValue = elementData(index);
//直接根据索引覆盖值
elementData[index] = element;
return oldValue;
}


七:index0f()
/**
* 从首部开始查找,返回第一个值相同的索引,遍历完没找到返回-1
*/
public int indexOf(Object o) {
//传进来的值为null,从首部开始遍历数组,找到第一个为null的值,返回当前索引
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//不为空,从首部遍历,返回第一个值相同的下标索引
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
//没找到返回-1
return -1;
}


本文适当参考下面两篇文章分析方法:


有疑问,扫我二维码添加微信,欢迎骚扰!
坚持做一件事,一起学习。


jdk1.8-ArrayList源码分析的更多相关文章

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

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

  2. Java基础 ArrayList源码分析 JDK1.8

    一.概述 本篇文章记录通过阅读JDK1.8 ArrayList源码,结合自身理解分析其实现原理. ArrayList容器类的使用频率十分频繁,它具有以下特性: 其本质是一个数组,因此它是有序集合 通过 ...

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

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

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

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

  5. ArrayList 源码分析

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

  6. ArrayList源码分析超详细

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

  7. Java - ArrayList源码分析

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

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

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

  9. Java ArrayList源码分析(有助于理解数据结构)

    arraylist源码分析 1.数组介绍 数组是数据结构中很基本的结构,很多编程语言都内置数组,类似于数据结构中的线性表 在java中当创建数组时会在内存中划分出一块连续的内存,然后当有数据进入的时候 ...

  10. Java入门系列之集合ArrayList源码分析(七)

    前言 上一节我们通过排队类实现了类似ArrayList基本功能,当然还有很多欠缺考虑,只是为了我们学习集合而准备来着,本节我们来看看ArrayList源码中对于常用操作方法是如何进行的,请往下看. A ...

随机推荐

  1. Java中的equals,==,compareTo和compare的比较

    Java中的equals(),==,compareTo()和compare() 首先只有==可以用作两个基本类型数据之间的比较,当然是值比较.当用作两个对象比较时,比较的是对象引用,而不是值比较. 其 ...

  2. 解决git提交敏感信息(回退git版本库到某一个commit)

    解决git提交敏感信息(回退git版本库到某一个commit) Fri 07 June 2013 git是一个很好的版本库, 现在很多人用它, 并在github上创建项目, 相信大家都有过将敏感信息提 ...

  3. Hadoop-No.9之表和Region

    影响性能与数据分布的一个因素是HBase中表的数量以及每个表的Region的数量.如果分配的不合理,集群一个节点活多个节点的负载会出现显著的不均衡. 其中比较注意的几点: - 每个节点包含一个Regi ...

  4. beautifulsoap常用取节点方法

    取某个class的元素 soup.find('div', {'class', 'description'}) 取某个属性的值 download_content.find('li').find('a') ...

  5. jquery load() 方法 语法

    jquery load() 方法 语法 作用:当指定的元素(及子元素)已加载时,会发生 load() 事件.该事件适用于任何带有 URL 的元素(比如图像.脚本.框架.内联框架).根据不同的浏览器(F ...

  6. ES大批量写入提高性能的策略

    1.用bulk批量写入 你如果要往es里面灌入数据的话,那么根据你的业务场景来,如果你的业务场景可以支持让你将一批数据聚合起来,一次性写入es,那么就尽量采用bulk的方式,每次批量写个几百条这样子. ...

  7. java 项目 文件关系 扫描 注释注入(3)

    @RequestParam和@PathVariable用法小结  https://www.cnblogs.com/helloworld-hyyx/p/5295514.html(copy) @Reque ...

  8. java批量下载

    最近做了一些有关批量压缩下载的功能,网上也找了一些资源,但都不是太全面,所以自己整理一份,已备不时之需. 直接上代码: // 获取项目路径     private static String WEBC ...

  9. Java进阶知识02 Struts2下的拦截器(interceptor)和 过滤器(Filter)

    一.拦截器 1.1.首先创建一个拦截器类 package com.bw.bms.interceptor; import com.opensymphony.xwork2.ActionContext; i ...

  10. HTML5属性备忘单

    在网上闲逛的时候看到了文章,感觉总结的这个html5文章,决定转载过来,在排版的时候也帮助自己重新梳理复习一遍.毕竟学习基础最重要. by zhangxinxu from http://www.zha ...