一: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;}

本文适当参考下面两篇文章分析方法:
https://www.cnblogs.com/leesf456/p/5308358.html
http://cmsblogs.com/?p=108
有疑问,扫我二维码添加微信,欢迎骚扰!
坚持做一件事,一起学习。

转载于:https://www.cnblogs.com/lizb0907/p/10342760.html

jdk1.8-ArrayList源码分析相关推荐

  1. 【Java源码分析】Java8的ArrayList源码分析

    Java8的ArrayList源码分析 源码分析 ArrayList类的定义 字段属性 构造函数 trimToSize()函数 Capacity容量相关的函数,比如扩容 List大小和是否为空 con ...

  2. ArrayList源码分析与手写

    本节主要分析JDK提供的ArrayList的源码,以及与自己手写的ArrayList进行对比. ArrayList源码分析 构造方法 private static final int DEFAULT_ ...

  3. Java集合Collection源码系列-ArrayList源码分析

    Java集合系列-ArrayList源码分析 文章目录 Java集合系列-ArrayList源码分析 前言 一.为什么想去分析ArrayList源码? 二.源码分析 1.宏观上分析List 2.方法汇 ...

  4. ArrayList 源码分析

    公众号原文:ArrayList 源码分析 博客原文:ArrayList 源码分析 以下源码分析使用的 Java 版本为 1.8 1. 概览 ArrayList 是基于数组实现的,继承 Abstract ...

  5. Java源码详解五:ArrayList源码分析--openjdk java 11源码

    文章目录 注释 类的继承与实现 构造函数 add操作 扩容函数 remove函数 subList函数 总结 本系列是Java详解,专栏地址:Java源码分析 ArrayList 官方文档:ArrayL ...

  6. java list addall源码_Java集合:ArrayList源码分析

    其实我看到已有很多大佬写过此类文章,并且写的也比较清晰明了,那我为何要再写一遍呢?其实也是为了加深本身的印象,巩固本身的基础html (主要是不少文章没有写出来我想知道的东西!!!​!!!!)java ...

  7. 面试必会之ArrayList源码分析手写ArrayList

    作者:Java知音-微笑面对生活 简介 ArrayList是我们开发中非常常用的数据存储容器之一,其底层是数组实现的,我们可以在集合中存储任意类型的数据,ArrayList是线程不安全的,非常适合用于 ...

  8. Java中ArrayList源码分析

    一.简介 ArrayList是一个数组队列,相当于动态数组.每个ArrayList实例都有自己的容量,该容量至少和所存储数据的个数一样大小,在每次添加数据时,它会使用ensureCapacity()保 ...

  9. 扩容是元素还是数组_02 数组(附ArrayList源码分析)

    定义 用一组连续的内存空间存储一组具有相同类型的数据的线性表数据结构. 优势 支持通过下标快速的随机访问数据,时间复杂度为O(1). 劣势 通常情况下,插入和删除效率低下,每次操作后,需要进行后续元素 ...

  10. ArrayList源码分析(基于JDK1.6)

    不积跬步,无以至千里:不积小流,无以成江海.从基础做起,一点点积累,加油! <Java集合类>中讲述了ArrayList的基础使用,本文将深入剖析ArrayList的内部结构及实现原理,以 ...

最新文章

  1. mysql索引 钱缀_mysql字符串前缀索引
  2. 当git上只做文件大小写重命名的修改时,如何躲坑...
  3. C++服务器设计(七):聊天系统服务端实现
  4. 分享下自己编译 XBMC 的过程(zhuan)
  5. 阿里云Redis混合存储典型场景:如何轻松搭建视频直播间系统
  6. 微信公众号中选择时间css,微信公众号到底应该几点推文?
  7. 如何订阅MVP on dot NET(或其它播客) - iTunes版
  8. ArrayList(Map(k v))相关操作和遍历
  9. shell 脚本 简易for循环 脚本
  10. Ext的组件模型印象
  11. 文档化ring3 api列举驱动列表 --- 做了一些重构。(解决内存泄漏问题)
  12. oracle linux下数据迁移到不同服务器
  13. 如何用EasyRecovery找回回收站信息(附注册机下载地址)
  14. linux ubuntu下网络调试助手(GUI)工具
  15. JQuery Mobile试试水
  16. 一步一步学网络爬虫(从python到scrapy)
  17. 数据库--视图的基本概念以及作用
  18. Java语言每日一练—第14天:银行收入计算
  19. 26个英语单词起源(百度百科+巴士英语)
  20. python数组堆叠,堆叠数组-python数据处理

热门文章

  1. word中的表格如何修改文字方向
  2. 从零搭建一个基于React+Nextjs的SSR网站(四):如何搭建服务器并部署Nextjs项目
  3. TCP/IP——网络层
  4. 基于深度学习的服装图像分类与检索
  5. python pymssql — pymssql模块使用指南
  6. pymssql连mysql_Python利用pymssql访问mysql数据库
  7. java语言产生_在java语言中产生正确的行为
  8. 露营“卖水人”,还能火多久?
  9. element UI-表格数据上下移动功能
  10. java sql绑定_在JAVA 源程序中编写SQL语句时使用ORACLE 绑定变量