ArrayList总结及部分源码分析
ArrayList源码阅读笔记
1. ArrayList继承的抽象类和实现的接口
- ArrayList类实现的接口
- List接口:里面定义了List集合的基本接口,ArrayList进行了实现
- RandomAccess接口
- Cloneable接口
- Serializable:标记该类支持序列化
- ArrayList继承了AbstractList抽象类
2. ArrayList底层的数据结构
ArrayList底层是基于数组实现的
特点:
- 数组在内存中是一个内存地址连续的,固定内存大小的内存空间,并且ArrayList可以存放重复的元素。允许存储 一个null或者多个null。
优点:
- 数组查询元素的效率快,因为数组查询元素时只需要一个元素对应在数组中存储的下标就可以获取到对应位置的元素值。
- ArrayList是异步的,也就是说该集合线程不安全,但是执行效率快
缺点:
- 当向ArrayList集合中存储的新的元素时,长度已经不够时,就会触发ArrayList底层的扩容机制。其实底层就是数组扩容。因为数组扩容需要把旧的数组元素复制到新的数组中,所以在数据量特别大的时候,扩容的效率不高。
- 在指定位置添加和修改元素效率不高(尾部除外)。
- 例如:在数组的尾部以外的地方插入一个元素,那么为例保证整个数组中所有元素的连续存储性,就必须向将从该位置到尾部的所有元素进行后移一位,腾出一个空的位置,再把新的元素进行插入。删除也是,删除了指定位置的之后需要把从该元素到尾部的所有元素向前移动一个位置。
- ArrayList的所有方法都没有进行使用 synchronized 关键字进行修饰,这就代表该类的所有方法都不是同步的,在多线程情况下存着线程安全问题。
3. ArrayList适用的场景
对于频繁查询和修改集合中元素的场景非常适合,因为ArrayList的查询效率快。
单线程环境下,频繁对数据进行查询和修改时也适用。如果该多线程情况下可以使用Vector集合。该集合底层也是基于数组实现的,但是中会出现线程安全问题的方法使用了 synchronized 进行修饰,保证了线程安全。但是该集合目前已经基本废弃了。因为将方法进行同步了,那么效率就会下降。如果在多线程情况下还想使用ArrayList集合,这个时候可以创建一个线程安全的ArrayList集合
// 创建一个线程安全的ArrayList集合,在多线程情况下能保证线程安全
List<Object> list=Collections.synchronizedList(new ArrayList<Object>());
4. ArrayList底层源码分析
1. ArrayList的三个构造函数
无参构造
/**
* 在创建对象时,如果不指定构造函数,将调用此构造函数初始化一个长度为0,类型为Object的数组
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
源码说明:无参构造器中用到的成员变量
- 在创建对象时,如果不指定构造函数,将调用此构造函数初始化一个长度为0,类型为Object的数组
// 一个 Object 类型的数组,可以存放任意类型的数据,初始化大小为0
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 一个 Object 类型的数组,可以存放任意类型的数据,不参与集合的序列化
transient Object[] elementData;
有参构造(传入一个整数,指定初始的集合长度大小)
/**
* 创建对象时,如果指定初始大小将掉有该方法,创建一个有指定初始换长度的ArrayList集合
* 如果提前知道需要存储的数据量有多少,使用该构造函数,可以改善因为频繁扩容导致的性能下降问题
* @param initialCapacity 创建对象时,指定的初始化长度
* @throws IllegalArgumentException 如何传入的数字小于0,则抛出异常,非法参数异常
*/
public ArrayList(int initialCapacity) {
// 判断传入的参数是否大于0
if (initialCapacity > 0) {
// 创建一个Object类型,长度为 initialCapacity 的数组,赋给 elementData 数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) { // 判断传入的参数是否等于0
// 一个 Object 类型的数组,可以存放任意类型的数据,初始化大小为 0
this.elementData = EMPTY_ELEMENTDATA;
} else { // 以上两个条件都不满足,即传入的参数小于0,抛出非法参数异常
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
源码说明:
// 一个 Object 类型的数组,可以存放任意类型的数据,不参与集合的序列化
// 使用 writeObject() 来实现数据的序列化 和 readObject() 反序列化
transient Object[] elementData; // 一个 Object 类型的数组,可以存放任意类型的数据,初始化大小为0
private static final Object[] EMPTY_ELEMENTDATA = {};
有参构造(传入一个Collection类型的集合)
/**
* 在初始化的时候直接将一个集合传入,可以吧传入集合的元素全部复制到创建的新集合中
* @param c 传入的集合
* @throws NullPointerException 当传入的集合为空时,会抛出异常
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
2. 添加一条数据的完整流程
- 在每次添加数据时,如果数据是基本数据类型,会先将基本数据类型进行装箱操作,把基本数据类型转换成对应的包装类型(引用数据类型)
// 例如:集合中存放Integer数据类型,在进行add操作时,会先进行装箱操作
/**
* 将基本数据类转换为引用数据类型
* @param i 传入的参数为一个基本型数据类型
* @return 返回的参数是一个基本数据类型的包装类(引用数据类型)
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
- 调用add方法进行添加操作
/**
* 添加元素方法,成功返回true,失败会抛出异常
* @param e 需要进行添加的元素值
* @return <tt>true</tt> 添加成功返回true,添加过程中如果失败会抛出异常
*/
public boolean add(E e) {
// 判断是否需要扩容的
ensureCapacityInternal(size + 1);
// 将传递进来的元素添加到数组的末尾
elementData[size++] = e;
// 返回true代表添加成功
return true;
}
变量说明:
/**
* 该变量用于记录当前ArrayList中实际存储的元素个数
*/
private int size;
- add方法中调用 ensureCapacityInternal 方法
/**
* 传入一个经过当前数组实际元素个数加 1 过后的参数,这样做主要是为了判断当前数组是否还支持添加新元素
* @param minCapacity 当前数组实际元素个数加 1 过后的参数
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
- 先调用 calculateCapacity 方法,在调用 ensureExplicitCapacity
calculateCapacity 方法
/**
* 用于确定存储元素的数组需要的长度,或者当该数组为空时做长度初始化处理
* @param elementData 当前用于存放元素的数组对象
* @param minCapacity 当前数组实际元素个数加 1 过后的参数
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 判断当前存储元素的数组是否为空
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 返回 DEFAULT_CAPACITY(10) 和 minCapacity 两个数中大的数,如果初始化是采用无参构造器,
// 则应当返回10,也就是经过判断之后存储数据的数组需要的容量大小
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 如果当前存储元素的数组不为空则直接返回当前数组实际元素个数加 1 过后得到的值
return minCapacity;
}
ensureExplicitCapacity 方法
/**
* 按照传递进来的具体值,判断是否需要进行扩容操作
* @param minCapacity 经过判断之后存储数据的数组需要的容量大小
*/
private void ensureExplicitCapacity(int minCapacity) {
// 用于记录数组被修改的次数
modCount++; // 如果实际需要的数组长度 - 数组当前的实际长度 > 0,说明该数组是要进行扩容
if (minCapacity - elementData.length > 0)
// 核心的扩容方法
grow(minCapacity);
}
- 判断是否需要扩容的核心方法 grow
/**
* 具体的扩容方法
* @param minCapacity 经过判断之后存储数据的数组需要的容量大小
*/
private void grow(int minCapacity) { // 获取为扩容之前的数组长度
int oldCapacity = elementData.length;
// 扩容之后的新数组长度为旧数组长度的 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新数组长度 - 旧数组的长度 < 0,这种情况发生在创建ArrayList时使用的构造器是默认的无参构造器
// 所以在第一次添加元素时 elementData.length == 0,
if (newCapacity - minCapacity < 0)
// 此时的 minCapacity 是在执行 calculateCapacity 方法时得到的数值10
newCapacity = minCapacity; // 当扩容之后的数组长度大于最大的长度时
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity); // 使用数组的 copyOf 对数组进行扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
变量说明:
// 整数类型的最大值减8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
- 当数组要求的扩容的长度过设置的最大值时
/**
* 具体的扩容方法
* @param minCapacity 经过判断之后存储数据的数组需要的容量大小
*/
private static int hugeCapacity(int minCapacity) {
// 是否已经内存溢出
if (minCapacity < 0)
throw new OutOfMemoryError(); // 判断实际需要的数组长度是否大于设置的最大值,是则返回最大值整数类型的最大值,否则返回设置的最大值
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
ArrayList总结及部分源码分析的更多相关文章
- ArrayList实现原理及源码分析之JDK8
转载 ArrayList源码分析 一.ArrayList介绍 Java 集合框架主要包括两种类型的容器: 一种是集合(Collection),存储一个元素集合. 一种是图(Map),存储键/值对映射. ...
- ArrayList相关方法介绍及源码分析
目录 ArrayList简介: ArrayList 相关方法介绍 代码表示 相关方法源码分析 ArrayList简介: java.util.ArrayList 是我们最常用的一个类,ArrayList ...
- 4.Java集合-ArrayList实现原理及源码分析
一.ArrayList概述: ArrayList 是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存 ArrayList不是线程安全的,只能用在单线程的情况 ...
- java ArrayList 迭代器快速失败源码分析
先来看一个例子: @Test void test2() { ArrayList<String> list = new ArrayList<String>(); list.add ...
- ArrayList增加扩容问题 源码分析
public class ArrayList<E>{ private static final int DEFAULT_CAPACITY = 10;//默认的容量是10 private s ...
- 大杂烩 -- ArrayList的动态增长 源码分析
基础大杂烩 -- 目录 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 问题:当ArrayList中放入的元素一直增加会如 ...
- ArrayList用法详解与源码分析
说明 此文章分两部分,1.ArrayList用法.2.源码分析.先用法后分析是为了以后忘了查阅起来方便-- ArrayList 基本用法 1.创建ArrayList对象 //创建默认容量的数组列表(默 ...
- JDK源码分析系列02---ArrayList和LinkList
ArrayList和LinkList的源码分析 概要 ArrayList和LinkList是常用的存储结构,不看源码先分析字面意思,Array意思是数组,可知其底层是用数组实现的,Link意思是链接, ...
- 【集合框架】JDK1.8源码分析之ArrayList(六)
一.前言 分析了Map中主要的类之后,下面我们来分析Collection下面几种常见的类,如ArrayList.LinkedList.HashSet.TreeSet等.下面通过JDK源码来一起分析Ar ...
随机推荐
- 要想玩转FPGA,按这4个步骤来
FPGA 作为一种高新技术,由于其结构的特殊性,可以重复编程,开发周期较短,越来越受到电子爱好者的青睐,其应用已经逐渐普及到了各行各业.因此,越来越多的学生或工程师都希望跨进FPGA的大门掌握这门技术 ...
- 绘制PCB电路原理图的8种方法
1.选择集成电路,变压器,晶体管等组件,这些组件体积庞大,有许多引脚并在电路中起主要作用,然后从选定的参考引脚中抽取,以减少错误. 2.如果PCB上标有元件编号(如VD870,R330,C466等), ...
- 字符串与模式匹配算法(六):Needleman–Wunsch算法
一.Needleman-Wunsch 算法 尼德曼-翁施算法(英语:Needleman-Wunsch Algorithm)是基于生物信息学的知识来匹配蛋白序列或者DNA序列的算法.这是将动态算法应用于 ...
- mongoDB 的一般使用
理解 mongodb 也是nosql 的一种.他的数据存储类型是一种和json格式比较像的数据类型,可以看作就是json. mongodb 里的数据库都是一个单独的库.一般需要用的库都会设置自己的us ...
- prometheus(3)之grafan可视化展现
可视化UI界面Grafana的安装和配置 Grafana介绍 Grafana是一个跨平台的开源的度量分析和可视化工具,可以将采集的数据可视化的展示,并及时通知给告警接收方.它主要有以下六大特点: 1. ...
- 关于JDBC中查询方法的抽取
萌新的JAVA学习笔记[1] 先来张伊蕾娜镇场~~ 简单描述 起初我们的查询方法时分为单个查询和全部查询,过于局限与繁琐,如此一来我们能不能想一个办法将所有类型的查询抽取出来并整合成为一个单独的工具方 ...
- Go语言核心36讲(Go语言实战与应用三)--学习笔记
25 | 更多的测试手法 在本篇文章,我会继续为你讲解更多更高级的测试方法.这会涉及testing包中更多的 API.go test命令支持的,更多标记更加复杂的测试结果,以及测试覆盖度分析等等. 前 ...
- RabbitMQ 线上事故!慌的一批,脑袋一片空白。。。
前言 那天我和同事一起吃完晚饭回公司加班,然后就群里就有人@我说xxx商户说收不到推送,一开始觉得没啥.我第一反应是不是极光没注册上,就让客服通知商户,重新登录下试试.这边打开极光推送的后台进行检查. ...
- XenServer删除ISO存储!
1.用命令 df -hal 可以看到 ISO库是使用了10G的硬盘的 2.下面开始直接右键删除ISO,但看到资源还是占用着10G的 3.如果想把这10G的硬盘资源空出来的话,只要复制前面查找到挂载的路 ...
- <C#任务导引教程>练习一
//1,定位显示ASCI码值为30到119的字符using System;class Program{ static void Main() { int i, n = 0; ...