(1)可以查看大佬们的 详细源码解析 : 连接地址为 : https://blog.csdn.net/zhumingyuan111/article/details/78884746

(2)

ArrayList实现了List接口,继承了AbstractList,底层是数组实现的,一般我们把它认为是可以自增扩容的数组。它是非线程安全的,一般多用于单线程环境下(与Vector最大的区别就是,Vector是线程安全的,所以ArrayList 性能相对Vector 会好些),它实现了Serializable接口,因此它支持序列化,能够通过序列化传输(实际上java类库中的大部分类都是实现了这个接口的),实现了RandomAccess接口,支持快速随机访问(只是个标注接口,并没有实际的方法),这里主要表现为可以通过下标直接访问(底层是数组实现的,所以直接用数组下标来索引),实现了Cloneable接口,能被克隆。
ArrayList:

RandomAccess:
初始化
ArrayList一共提供了三个初始化的方法:
  1. public ArrayList()
  2. public ArrayList(Collection<? extends E> c)
  3. public ArrayList(int initialCapacity);
首先看看源码里无参构造方法的实现:
上面的注释表示他会默认提供容量为10的数组,但是实际并不是在这一步实现。可以看看这里的DEFAULTCAPACITY_EMPTY_ELEMENTDATA和elementData:
只是一个空数组。所以这一步实际上只是将elementData指向一个空数组而已。
再来看看带参数的构造方法
这个方法是直接将一个集合作为ArrayList的元素,很容易看懂,不多做解释,此时elementData即为集合c转为的数组,size即为elementData的长度。这里size是ArrayList的一个int型私有变量,用于记录该list集合中当前元素的数量,注意不是容量。
再来看看带初始化容量的构造方法:
从源码里可以看出:首先对传进来的初始化参数initialCapacity进行判断,如果该参数大于0,在elementData进行初始化,初始化为一个容量为initialCapacity的数组,如果传进来的参数initialCapacity等于0,则将elementData指向了EMPTY_ELEMENTDATA,从这个名字也可以猜出,是个空数组:
add方法的实现
说了这么多,还没有说到无参构造函数默认是空数组,为什么注释说是容量为10的数组,也还没说到当容量不足时,是如何实现动态扩容的,下面就通过add方法来说明这些问题。(add方法是list接口中声明的通用方法)。ArrayList的add方法实现如下:
size是当前集合拥有的元素个数(未算进准备新增的e元素),从源码看出,调用了ensureCapacityInternal来保证容量问题,传进去的参数是size+1,来保证新增元素后容量满足要求。
接下来进入ensureCapacityInternal方法查看其实现:
可以看到代码段:
  1. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  2. minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  3. }
通过这一步来判断,当前elementData是否为空数组(即初始化容量为0或者调用了无参构造函数后的结果),如果是,则使用
Math.max(DEFAULT_CAPACITY, minCapacity)进行选择一个较大的,其中,DEFAULT_CAPACITY是ArrayList定义的静态常量10:
可以看出,这里如果minCapacity小于10的话(如果elementData为空的话,size+1即minCapacity一般为1),返回的是10,所以如果没有指定大小的话,默认是初始化一个容量为10的数组。然后在调用ensureExplicitCapacity方法:
可以看到modCount++,这里可以暂时不管它,这个参数主要是用在集合的Fail-Fast机制(即快速失败机制)的判断中使用的。(以后有空再补充这方面的内容)
在这个方法里进行判断,新增元素后的大小minCapacity是否超过当前集合的容量elementData.length,如果超过,则调用grow方法进行扩容。我们进入该方法进行查看:
在这里可以很清楚的看到扩容容量的计算:int newCapacity = oldCapacity + (oldCapacity
>> 1),其中oldCapacity是原来的容量大小,oldCapacity >> 1
为位运算的右移操作,右移一位相当于除以2,所以这句代码就等于int newCapacity = oldCapacity
+ oldCapacity /
2;即容量扩大为原来的1.5倍(注意我这里使用的是jdk1.8,没记错的话1.7也是一样的),获取newCapacity后再对newCapacity的大小进行判断,如果仍然小于minCapacity,则直接让newCapacity
等于minCapacity,而不再计算1.5倍的扩容。然后还要再进行一步判断,即判断当前新容量是否超过最大的容量 if
(newCapacity - MAX_ARRAY_SIZE >
0),如果超过,则调用hugeCapacity方法,传进去的是minCapacity,即新增元素后需要的最小容量:
如果minCapacity大于MAX_ARRAY_SIZE,则返回Integer的最大值。否则返回MAX_ARRAY_SIZE。
然后回到grow方法,调用Arrays.copyof方法,即复制原数组内容到一个新容量的大数组里。这里Arrays.copyof方法实际是调用System.arraycopy方法。
到这里,应该可以很清楚的知道ArrayList底层扩容的原理了。与Vector不同的是,Vector每次扩容容量是翻倍,即为原来的2倍,而ArrayList是1.5倍。看似1.5倍增长的很慢,那经常增加大量元素会不会导致经常扩容,数组重新分配导致效率低下呢?其实不然,每次增长为原来的1.5倍实际增长的量会越来越大的,可以看看网友统计的数据(参考:http://blog.csdn.net/java2000_net/article/details/5215882):
1千需要分配 11次
1万一级需要分配17次
10万 需要分配23次
100万需要分配28次
当然,如果一开始知道数据量很大的话,可以在初始化时预先指定容量。
get方法
很明显是通过数组下标索引来指定返回的数组,这里不多做解释。
验证
无参构造函数,add三个元素,
按照理解,此时默认容量应该为10:
可以看出:elementData容量为10,size为3。
 
无参构造函数,增加12个元素:
测试代码:
  1. List<Integer> list = new ArrayList<>();
  2. for (int i = 0 ; i < 12; i ++) {
  3. list.add(i);
  4. }
  5.  
  6. System.out.println("ok");
通过debug查看结果:
看到elementData扩容为15(10+10/2 = 15),而集合元素size为12。
 
无参构造函数,增加原来的1.5倍扩容量的数据:
测试代码:
  1. List<Integer> list = new ArrayList<>();
  2. List<Integer> newList = new ArrayList<>();
  3. for (int i = 0 ; i < 5; i ++) { 初始5个
  4. list.add(i);
  5. }
  6. for (int i = 5 ; i < 20; i ++) {
  7. newList.add(i);
  8. }
  9. list.addAll(newList); //一次性增加15个
  10. System.out.println("ok");
正常情况下,新增5个后,容量为10,再次新增,会变为原来的1.5倍,即15,但是这里新增15个,明显超过,按照上面的理解,应该直接让新容量等于需要的最小容量20,从测试截图可以看到,结果正确。
 
带集合参数构造函数:
测试代码:
  1. List<Integer> newList = new ArrayList<>();
  2. for (int i = 5 ; i < 20; i ++) {
  3. newList.add(i);
  4. }
  5. List<Integer> list = new ArrayList<>(newList);
  6. System.out.println("ok");
测试结果:
可以看到,结果elementData的容量即为集合参数的大小。
总结
总之,ArrayList默认容量是10,如果初始化时一开始指定了容量,或者通过集合作为元素,则容量为指定的大小或参数集合的大小。每次扩容为原来的1.5倍,如果新增后超过这个容量,则容量为新增后所需的最小容量。如果增加0.5倍后的新容量超过限制的容量,则用所需的最小容量与限制的容量进行判断,超过则指定为Integer的最大值,否则指定为限制容量大小。然后通过数组的复制将原数据复制到一个更大(新的容量大小)的数组。
附:
size和modCount的区别
可能看了源码有时候还分不清size和modCount的区别,那么这里就用例子来说明。
size是ArrayList的变量。modCount是ArrayList的父类AbstractList中的变量,默认值为0。
size记录了ArrayList中元素的数量,modCount记录的是关于元素的数目被修改的次数。modCount在ArrayList的普通操作里可能并没有看出多大用处,但是在涉及到fail-fast就主要是依靠它了。
直接用下面这段代码进行测试:
  1. List<Integer> list = new ArrayList<>();
  2. list.add(1);
  3. list.add(2);
  4. list.add(3);
  5. list.add(4);
  6. list.remove(2);
  7. list.add(5);
  8. list.set(1, 100);
  9. list.remove(4);
  10. System.out.println(list.size());
当执行完 list.add(4)时,此时modCount和size都为4:
当执行完list.remove(2)时,此时元素数量发生了修改,所以modCount++即5,而size记录集合中元素的个数,移除了一个后,size=size-1即3:

当执行完list.add(5)时,此时元素数量再次发生了修改,所以modCount++即5,而size记录集合中元素的个数,增加了一个后,size=size-1即4:

当执行完list.set(1, 100)时,元素的数量并没有发生改变,所以modCount和size都不变。

Java 集合源代码——ArrayList的更多相关文章

  1. 从源码看Java集合之ArrayList

    Java集合之ArrayList - 吃透增删查改 从源码看初始化以及增删查改,学习ArrayList. 先来看下ArrayList定义的几个属性: private static final int ...

  2. Java集合源代码剖析(一)【集合框架概述、ArrayList、LinkedList、Vector】

    Java集合框架概述 Java集合工具包位于Java.util包下.包括了非常多经常使用的数据结构,如数组.链表.栈.队列.集合.哈希表等.学习Java集合框架下大致能够分为例如以下五个部分:List ...

  3. Java集合:ArrayList的实现原理

    Java集合---ArrayList的实现原理   目录: 一. ArrayList概述 二. ArrayList的实现 1) 私有属性 2) 构造方法 3) 元素存储 4) 元素读取 5) 元素删除 ...

  4. Java集合关于ArrayList

    ArrayList实现源码分析 2016-04-11 17:52 by 淮左, 207 阅读, 0 评论, 收藏, 编辑 本文将以以下几个问题来探讨ArrayList的源码实现1.ArrayList的 ...

  5. Java集合源代码剖析(二)【HashMap、Hashtable】

    HashMap源代码剖析 ; // 最大容量(必须是2的幂且小于2的30次方.传入容量过大将被这个值替换) static final int MAXIMUM_CAPACITY = 1 << ...

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

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

  7. java集合之ArrayList,TreeSet和HashMap分析

    java集合是一个重点和难点,如果我们刻意记住所有的用法与区别则是不太现实的,之前一直在使用相关的集合类,但是没有仔细研究区别,现在来把平时使用比较频繁的一些集合做一下分析和总结,目的就是以后在需要使 ...

  8. 【源码阅读】Java集合之一 - ArrayList源码深度解读

    Java 源码阅读的第一步是Collection框架源码,这也是面试基础中的基础: 针对Collection的源码阅读写一个系列的文章,从ArrayList开始第一篇. ---@pdai JDK版本 ...

  9. 【Java集合源代码剖析】Java集合框架

    转载轻注明出处:http://blog.csdn.net/ns_code/article/details/35564663 Java集合工具包位于Java.util包下,包括了非常多经常使用的数据结构 ...

随机推荐

  1. 2019-9-2-Visual-Studio-自定义项目模板

    title author date CreateTime categories Visual Studio 自定义项目模板 lindexi 2019-09-02 12:57:38 +0800 2018 ...

  2. 2018-8-10-WPF-判断调用方法堆栈

    title author date CreateTime categories WPF 判断调用方法堆栈 lindexi 2018-08-10 19:16:53 +0800 2018-2-13 17: ...

  3. Springboot上传文件临时目录无效

    一个奇葩问题,虽然解决了,但还是没弄清楚,小记一笔. 年后回来,测试人员对年前的3次迭代的功能进行了回归测试,然后发现所有excel导入的功能都失效了.作为后台开发人员,当然是第一时间打开运行日志排查 ...

  4. 【38.96%】【hdu 1540】Tunnel Warfare

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

  5. 2018.11.25 齐鲁工业大学ACM-ICPC迎新赛正式赛题解

    整理人:周翔 A 约数个数(难) 解法1:苗学林  解法2:刘少瑞   解法3:刘凯  解法4:董海峥 B Alice And Bob(易) 解法1:周翔  解法2:苗学林  解法3:刘少瑞 C 黑白 ...

  6. next-i18next 常见Bug记录

    TypeError: Cannot read property 'wait' of null 此处根本原因为next版本(使用withNamespaces导入命名空间报错) ^5.0.0版本不支持导入 ...

  7. OPENWRT X86 安装使用教程 (未完成)

    目 录  一 下载 Openwrt 镜像文件 二 将镜像文件写入目标磁盘 2.1  写盘工具 2.2 Physdiskwrite 写盘 2.3 win32diskimager 写盘 三 管理界面 3. ...

  8. CP防火墙排错装逼三件套

    1.tcpdump 通常用来抓包处理经过网卡的交互包 [Expert@BJ-OFFICE-GW:0]# tcpdump -nni any host 10.158.1.100 -w /var/log/t ...

  9. CP防火墙导入.csv格式的对象

    Step1:将.csv格式的对象上传到管理服务器,本例为/home/admin目录 [Expert@SZ-OFFICE-SMT:0]# pwd/home/admin[Expert@SZ-OFFICE- ...

  10. Oracle Net Manager 的使用方法(监听的配置方法)

    一,在服务端配置oracle端口 win+R  输入netca 弹出如下窗口后 选择监听程序配置,点击下一步 二.配置端口后使用Telnet工具调试端口是否联通 在命令行输入telnet 服务器ip ...