Java源码分析之ArrayList
ArrayList是以数组为基准的容器类,和LinkedList(链表)正好相反。因而ArrayList拥有更好的查找性能,增删操作则差一些。ArrayList封装了对于常规数组的操作,同时可以自动扩展容量。
下面对ArrayList的API进行归类:
1、构造函数:
①ArrayList() 以空数组进行构造
②ArrayList(int) 以指定大小的容量初始化数组
③ArrayList(Collection) 以指定集合构造ArrayList的数组元素
2、增加元素:
①boolean add(E) 在数组末尾加入指定元素
②void add(int,E) 在第一个参数指定的索引处插入元素,后面所有元素后移一个位置
③boolean addAll(Collection) 在数组末尾加入集合的所有元素
④boolean addAll(int,Collection) 在指定索引处加入集合所有元素
3、删除元素:
①E remove(int) 移除索引处元素,之后的所有元素前移一位
②boolean remove(Object) 查找到指定元素,删除之,未删除成功false
③void removeRange(int,int) 删除指定区间的所有元素,删除第一个索引处,但不删除最后一个索引处
④void clear() 删除所有元素
4、更改元素:
set(int,Object) 将指定索引处的值修改为指定的值
5、查找:
①E get(int) 返回指定索引处的元素
②boolean contains(Object) 确定是否包含该元素
③int indexOf(Object) 从前往后查找指定元素的索引,找不到返回-1
④int lastIndexOf(Object) 从后往前查找元素索引,找不到返回-1
⑤int size() 返回包含的元素个数
⑥boolean isEmpty() 确定数组是否为空
6、其他操作:
①Object[] toArray() 返回Object数组,每个Object对应ArrayList的一个元素
②T[] toArray(T[]) 返回T类型数组,每个T对应ArrayList的一个实际类型的元素
③void trimToSize() 删除数组最后冗余的值为null的元素
④void ensureCapacity(int) 使数组容量扩充为指定的容量
⑤Object clone() 复制一个除了内存地址其他信息完全一样的ArrayList
接下来进行源码解析
一、ArrayList包含的成员变量和常量:
transient Object[] array; //ArrayList的核心,所有操作都围绕它展开,ArrayList类似于数组的包装类
int size; //数组包含的实际元素个数,不是数组容量,为了满足添加元素时数组不必多次扩容,必须预先将容量设定为某个略大些的值。这样就不能用array.length得到元素个数了
二、构造函数3种
//不给参数,用空数组初始
public ArrayList() {
array = EmptyArray.OBJECT;
}
//用一个已有的容器对象初始化数组
public ArrayList(Collection<? extends E> collection) {
if (collection == null) {
throw new NullPointerException("collection == null");
} Object[] a = collection.toArray();
if (a.getClass() != Object[].class) {//这个过程看不懂
Object[] newArray = new Object[a.length];
System.arraycopy(a, 0, newArray, 0, a.length); //数组复制操作
a = newArray;
}
array = a;
size = a.length;
}
//用指定初始容量初始化数组,当容量可以确定的时候用这个方法可以避免多次更改数组大小,提高性能
public ArrayList(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("capacity < 0: " + capacity);
}
array = (capacity == 0 ? EmptyArray.OBJECT : new Object[capacity]);
}
三、增加元素
这应该是ArrayList中最复杂的操作了,因为涉及到容量扩充的操作,也是ArrayList和数组最大的区别——自动扩容。但是因为涉及到重新分配内存空间和数组整个复制,多次扩容会影响性能
@Override
public boolean add(E object) {
Object[] a = array; //用一个代表,减少书写量
int s = size;
if (s == a.length) { //数组已满,必须先扩容
//注意这边的扩容策略,必须先开辟一片更大的内存空间,再执行数组复制操作
//具体新数组取多大,如果当前容量小于最小容量增长值的一半,则按这个值增长;否则即原来容量足够大,在原来容量基础上增加一半
Object[] newArray = new Object[s +
(s < (MIN_CAPACITY_INCREMENT / 2) ?
MIN_CAPACITY_INCREMENT : s >> 1)];
System.arraycopy(a, 0, newArray, 0, s); //数组复制操作
array = a = newArray;
}
a[s] = object; //现在容量足够了,在新数组末尾直接加上这个新元素
size = s + 1; //别忘了现在元素个数加了一个,必须保持同步
modCount++; //修改次数加1
return true;
}
//对上面的扩容操作进行了抽象,可能因为上面的操作更频繁,所以直接把扩容操作写在代码里面以提高性能吧
private static int newCapacity(int currentCapacity) {
int increment = (currentCapacity < (MIN_CAPACITY_INCREMENT / 2) ?
MIN_CAPACITY_INCREMENT : currentCapacity >> 1);
return currentCapacity + increment;
} //在指定位置插入,除了数组复制操作的范围不同,其他都一样
@Override
public void add(int index, E object) {
Object[] a = array;
int s = size;
if (index > s || index < 0) {
throwIndexOutOfBoundsException(index, s);
} if (s < a.length) {
System.arraycopy(a, index, a, index + 1, s - index);
} else {
// assert s == a.length;
Object[] newArray = new Object[newCapacity(s)];
System.arraycopy(a, 0, newArray, 0, index);
System.arraycopy(a, index, newArray, index + 1, s - index);
array = a = newArray;
}
a[index] = object;
size = s + 1;
modCount++;
}
四、删除元素
//这里数组相比链表的劣势就很明显了,删除指定位置的值很简单,但是为了保持后面操作的稳定性,必须将之后的所有值前移一个位置,需要O(n)的时间,而链表只要O(1)。
//这里得到一个很重要的启示:如果要移除多个连续的元素,用for循环配合这个方法会非常损耗性能,应该用下面的removeRange方法
@Override
public E remove(int index) {
Object[] a = array;
int s = size;
if (index >= s) {
throwIndexOutOfBoundsException(index, s);
}
@SuppressWarnings("unchecked")
E result = (E) a[index]; //因为每个元素都是Object类型,必须强转
System.arraycopy(a, index + 1, a, index, --s - index);
a[s] = null; // 防止内存占用过多,不用的值置为null,给系统及时回收
size = s;
modCount++;
return result;
} //移除连续区间的元素,只需执行一次arrayCopy操作
@Override protected void removeRange(int fromIndex, int toIndex) {
if (fromIndex == toIndex) {
return;
}
Object[] a = array;
int s = size;
if (fromIndex >= s) {
throw new IndexOutOfBoundsException("fromIndex " + fromIndex
+ " >= size " + size);
}
if (toIndex > s) {
throw new IndexOutOfBoundsException("toIndex " + toIndex
+ " > size " + size);
}
if (fromIndex > toIndex) {
throw new IndexOutOfBoundsException("fromIndex " + fromIndex
+ " > toIndex " + toIndex);
} System.arraycopy(a, toIndex, a, fromIndex, s - toIndex);
int rangeSize = toIndex - fromIndex;
Arrays.fill(a, s - rangeSize, s, null); //不用的索引处全部置null
size = s - rangeSize;
modCount++;
} //需要依次对每一个元素进行比较,性能也不好
//可以看出这个方法只会删除第一个出现的元素,如果存在equals比较后相同的元素,还有遗留
@Override
public boolean remove(Object object) {
Object[] a = array;
int s = size;
if (object != null) {
for (int i = 0; i < s; i++) {
if (object.equals(a[i])) {
System.arraycopy(a, i + 1, a, i, --s - i);
a[s] = null; // Prevent memory leak
size = s;
modCount++;
return true;
}
}
} else {
for (int i = 0; i < s; i++) {
if (a[i] == null) {
System.arraycopy(a, i + 1, a, i, --s - i);
a[s] = null; // Prevent memory leak
size = s;
modCount++;
return true;
}
}
}
return false;
}
//这里可以看出是通过将每个元素依次置为null,也需要O(n)时间;并不改变数组容量
@Override
public void clear() {
if (size != 0) {
Arrays.fill(array, 0, size, null);
size = 0;
modCount++;
}
}
五、修改元素值
//修改元素值很简单,跟数组操作一样,只要O(1)时间就能完成,同时返回被修改的旧值
@Override
public E set(int index, E object) {
Object[] a = array;
if (index >= size) {
throwIndexOutOfBoundsException(index, size);
}
@SuppressWarnings("unchecked")
E result = (E) a[index];
a[index] = object;
return result;
}
六、查找操作
//查找操作是ArrayList最大的优势,因为数组在内存中占用连续的存储区域,只要O(1)时间就能找到指定索引所对应的值
@SuppressWarnings("unchecked")
@Override
public E get(int index) {
if (index >= size) {
throwIndexOutOfBoundsException(index, size);
}
return (E) array[index];
}
七、其他操作
关于ArrayList向数组转换(其实是返回ArrayList的成员数组的拷贝)
/*这两个方法经常会搞混,包括我初学的时候也不知道是怎么回事。第一种方法貌似很简单,但是因为返回的是Object数组,后续如果要对这个返回的数组操作一般要转换为具体的类型,就还要对该数组的每个元素都强转一次。
而第二个方法传入一个给定的数组,这个数组是指定了具体的类型的,因而返回后这个数组可以直接用。通常传入一个空数组 new T[]{}即可。
*/
@Override
public Object[] toArray() {
int s = size;
Object[] result = new Object[s];
System.arraycopy(array, 0, result, 0, s);
return result;
} /*这里有一点让我不明白,为什么泛型要选择T而不是E?如果T和E不一致,arraycopy操作会成功吗?*/
@Override
public <T> T[] toArray(T[] contents) {
int s = size;
if (contents.length < s) { //如果提供的数组不够放,重新开辟内存
@SuppressWarnings("unchecked")
T[] newArray
= (T[]) Array.newInstance(contents.getClass().getComponentType(), s);
contents = newArray;
}
System.arraycopy(this.array, 0, contents, 0, s);
if (contents.length > s) {
contents[s] = null; //这里也让我不明白,为什么只把s处置为null,s以后的不也应该置为null吗?
}
return contents;
}
Java源码分析之ArrayList的更多相关文章
- java读源码 之 list源码分析(ArrayList)---JDK1.8
java基础 之 list源码分析(ArrayList) ArrayList: 继承关系分析: public class ArrayList<E> extends AbstractList ...
- 【集合框架】JDK1.8源码分析之ArrayList详解(一)
[集合框架]JDK1.8源码分析之ArrayList详解(一) 一. 从ArrayList字表面推测 ArrayList类的命名是由Array和List单词组合而成,Array的中文意思是数组,Lis ...
- 集合源码分析[3]-ArrayList 源码分析
历史文章: Collection 源码分析 AbstractList 源码分析 介绍 ArrayList是一个数组队列,相当于动态数组,与Java的数组对比,他的容量可以动态改变. 继承关系 Arra ...
- Java源码系列1——ArrayList
本文简单介绍了 ArrayList,并对扩容,添加,删除操作的源代码做分析.能力有限,欢迎指正. ArrayList是什么? ArrayList 就是数组列表,主要用来装载数据.底层实现是数组 Obj ...
- Java源码分析 | CharSequence
本文基于 OracleJDK 11, HotSpot 虚拟机. CharSequence 定义 CharSequence 是 java.lang 包下的一个接口,是 char 值的可读序列, 即其本身 ...
- Java集合源码分析之ArrayList
ArrayList简介 从上图可以看到,ArrayList是集合框架中List接口的一个实现类,它继承了AbstractList类,实现了List, RandomAccess, Cloneable, ...
- 【集合框架】JDK1.8源码分析之ArrayList(六)
一.前言 分析了Map中主要的类之后,下面我们来分析Collection下面几种常见的类,如ArrayList.LinkedList.HashSet.TreeSet等.下面通过JDK源码来一起分析Ar ...
- Java源码分析之LinkedList
LinkedList与ArrayList正好相对,同样是List的实现类,都有增删改查等方法,但是实现方法跟后者有很大的区别. 先归纳一下LinkedList包含的API 1.构造函数: ①Linke ...
- Java源码分析:关于 HashMap 1.8 的重大更新(转载)
http://blog.csdn.net/carson_ho/article/details/79373134 前言 HashMap 在 Java 和 Android 开发中非常常见 而HashMap ...
随机推荐
- 使用FlexPaper实现office文件的预览(C#版)
需求很简单,用户上传office文件(word.excel.ppt)后,可以预览上传的这些文件.搜索的相关的资料后.整理如下: Step1.用户上传office文件. Step2.把Office文件转 ...
- 移动端IM系统的协议选型:UDP还是TCP?
1.前言 对于有过网络编程经验的开发者来说,使用何种数据传输层协议来实现数据的通信,是个非常基础的问题,它涉及到你的第一行代码该如何编写. 从PC时代的IM开始,IM开发者就在为数据传输协议的选型争论 ...
- knockoutjs+ jquery pagination+asp.net web Api 实现无刷新列表页
Knockoutjs 是一个微软前雇员开发的前端MVVM JS框架, 具体信息参考官网 http://knockoutjs.com/ Web API数据准备: 偷个懒数据结构和数据copy自官网实例 ...
- iOS-重回block小白之路
在我刚刚接触iOS开发的时候,是通过MJ老师讲的OC基础入门的,iOS圈的人应该基本都知道MJ大神吧,即便如此大神,讲解完block之后我依然感觉晕晕乎乎的,直到后来真正进公司做项目,依然感觉这是自己 ...
- ASP.NET MVC入门之再不学习就真的out了
听说最近又出了什么SAM,MVC辉煌即将过去,惊了我一身冷汗,ASP.NET MVC是啥都还没搞明白呢 于是赶紧打开ASP.NET官网学习学习,欢迎各位高手大侠来指点指点
- 突如其来的"中断异常",我(Java)该如何处理?
一.何为异常? 1.生活中的实例 生活中存在许多不正常: 上班路上自行车掉链子 上厕所手机掉马桶 下班回家钥匙丢失 ....... 2.程序中的实例 我们的代码中也许存在许多纰漏,导致用户使用时程序突 ...
- fastq-dump 报错 解决方案
命令行: ~/tools/sratoolkit/sratoolkit.2.3.2-5-centos_linux64/bin/fastq-dump --split-spot --gzip rhesus_ ...
- MySQL中索引和优化的用法总结
1.什么是数据库中的索引?索引有什么作用? 引入索引的目的是为了加快查询速度.如果数据量很大,大的查询要从硬盘加载数据到内存当中. 2.InnoDB中的索引原理是怎么样的? InnoDB是Mysql的 ...
- Java--FutureTask原理与使用(FutureTask可以被Thread执行,可以被线程池submit方法执行,并且可以监控线程与获取返回值)
package com; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; i ...
- 华硕笔记本U盘启动系统/WinPE报错。Windows failed to start. A Recent hardware or software change might be the cause.
最近在整一台华硕笔记本,大概有5年寿命了吧,质量还行,由于系统出了问题,打算用自制U盘WinPE进去修复一下.按照个人经验,在主板设置里启用了USB启动选项,并且设置USB启动顺序为第一个,可是进系统 ...