ArrayList——源码探究
摘要

ArrayList 是Java中常用的一个集合类,其继承自AbstractList并实现了List, RandomAccess, Cloneable, java.io.Serializable四个接口。本文主要来探究一下ArrayList的代码实现。
构造器
ArrayList 提供了三个构造器,分别是
含参构造器-1
// 含参构造器-1
// 参数含义: 初始化线性表容量
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
含参构造器-2
// 含参构造器-2
// 参数含义:其他的集合类型转为ArrayList
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
无参构造器
// 无参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
成员变量
// 默认的表容量
private static final int DEFAULT_CAPACITY = 10;
// 构造参数为 0 的时候,默认都指向该数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 无参构造时默认指向该数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 实际存放数据的数组
transient Object[] elementData;
// 表大小
private int size;
// 修改次数 Modify Count (定义在AbstractList中) 【划重点】
protected transient int modCount = 0;
关于这两个空数和构造器,参考如下代码
ArrayList a1 = new ArrayList(0);
ArrayList a2 = new ArrayList();
ArrayList a3 = new ArrayList();
ArrayList a4 = new ArrayList(0);
Field field = ArrayList.class.getDeclaredField("elementData");
field.setAccessible(true);
System.out.println(field.get(a1) == field.get(a2));
System.out.println(field.get(a2) == field.get(a3));
System.out.println(field.get(a1) == field.get(a4));
以上代码将会输出如下结果
false # a1.elementData ≠ a2.elementData
true # a2.elementData = a3.elementData
true # a1.elementData = a4.elementData
内部类
// 迭代器实现类,实现了Iterator接口,可以使用增强的for循环
private class Itr implements Iterator<E>{}
// 需要注意的是:ListIterator接口继承于Iterator接口
// ListIterator 和 Iterator最大的区别在于:Iterator单向移动,而ListIterator可以双向移动并且可以增加、设置元素
// forEachRemaining 方法都是向后迭代的
private class ListItr extends Itr implements ListIterator<E>{}
// 为sublist()方法准备的内部类
private class SubList extends AbstractList<E> implements RandomAccess]{}
// 为多核应用开发所准备的Spliterator
static final class ArrayListSpliterator<E> implements Spliterator<E>{}
常用方法
扩容机制
ArrayList的扩容由以下几个方法来实现。要明确的是,ArrayList的容量实际上就是其存放数据的elementData数组的长度,每一次自动的扩容都是由当前表的大小和elementData的长度做对比后而决定的。
// 供使用者调用的容量初始化方法,在大量增加新数据的情况下,预先确定足够的容量可以减少时间开支(因为会因为表不够大而频繁的扩容)
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
// 最小能保证表容量为DEFAULT_CAPACITY(10)
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
// 这个方法供内部调用,也就是在add和addAll里使用的。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 按最大的算,此处也可以看出来,容量最小也是DEFAULT_CAPACITY(10)
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
// 不够大就扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
* 实际的扩容方法
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容实际最小扩的是1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity); // 最大也只能能是MAX_ARRAY_SIZE(有可能是Integer.MAX_VALUE)
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
add
- 默认增加到最后一个元素后面
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
- 在指定位置后面插入
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
addAll
- 默认增加到最后一个元素后面
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
- 在指定位置后面插入
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount,同时确认是否扩容
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
remove
- 按下标
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);
elementData[--size] = null; // clear to let GC do its work,防止内存泄露
return oldValue;
}
- 按元素
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work,防止内存泄露
}
set
- 指定位置
// 插入新值,返回旧值
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
get
- 指定位置
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
clear
public void clear() {
modCount++;
// 这里不能简单的把size置为0就完事,要确定本表对每一个元素不再有引用关系,避免内存泄漏
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
trimToSize
一般来说,表的大小要小于等于表的容量,而在内存紧张的时候,可能会用到该方法来使表的容量缩小至表的大小一样大。
public void trimToSize() {
modCount++;
// size 只会 ≤ elementData.length,而等于的时候该方法的目的已经达到就不用做任何事了
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
indexOf
public int indexOf(Object o) {
// 注意这里区分了 o 是否为 null
// 为null的情况很好理解,因为 null 的含义是确认的
// 但是不为空即是一个对象的话,就需要用到equals方法,所以一般的类会重写equals方法
// Object的equals只是简单的对引用做个比较
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;
}
return -1;
}
lastIndexOf
这个方法是与indexOf对应的一个方法,indexOf的实现是正序查找,而本方法是逆序查找
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
contains
这个方法的内部实现是依靠indexOf方法来实现的
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
sublist
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
// 这里就用到了前面列出的SubList内部类
// 需要注意的是:由sublist方法返回的List,对其操作时会在原List上生效,即它们是同一个元素
return new SubList(this, 0, fromIndex, toIndex);
}
补充方法
此外有四个补充的方法需要注意,这四个方法的参数都是函数式接口的实现类,即可以使用λ表达式
forEach
顾名思义,作用于每一个元素的方法
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
// 正向遍历
for (int i=0; modCount == expectedModCount && i < size; i++) {
// accept的原型为: void accept(T t); 即一个无返回值的函数,也是需要在λ表达式里表示的内容
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
removeIf
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
// figure out which elements are to be removed
// any exception thrown from the filter predicate at this stage
// will leave the collection unmodified
int removeCount = 0;
final BitSet removeSet = new BitSet(size);
final int expectedModCount = modCount;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
final E element = (E) elementData[i];
// test的原型为: boolean test(T t); 返回布尔值,在这里也能看出来,当返回的值为true时,才进行代码块的操作
if (filter.test(element)) {
removeSet.set(i);
removeCount++;
}
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
// shift surviving elements left over the spaces left by removed elements
final boolean anyToRemove = removeCount > 0;
if (anyToRemove) {
final int newSize = size - removeCount;
for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
i = removeSet.nextClearBit(i);
elementData[j] = elementData[i];
}
for (int k=newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work
}
this.size = newSize;
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
return anyToRemove;
}
replaceAll
public void replaceAll(UnaryOperator<E> operator) {
// UnaryOperator 一元运算符
Objects.requireNonNull(operator);
final int expectedModCount = modCount;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
// UnaryOperator<T> 继承自 Function<T,T(R)>
// 其apply 的原型为: R apply(T t);
// BinaryOperator<T> 继承自 BiFunction<T,T(U),T(R)>
// 其apply方法原型为: R apply(T t, U u);
elementData[i] = operator.apply((E) elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
sort
这里的排序是借助于Arrays这个工具类的sort方法来实现的,这里仅作参考
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
ArrayList——源码探究的更多相关文章
- 竟然还有人说ArrayList是2倍扩容,今天带你手撕ArrayList源码
ArrayList是我们开发中最常用到的集合,但是很多人对它的源码并不了解,导致面试时,面试官问的稍微深入的问题,就无法作答,今天我们一起来探究一下ArrayList源码. 1. 简介 ArrayLi ...
- java8 ArrayList源码阅读
转载自 java8 ArrayList源码阅读 本文基于jdk1.8 JavaCollection库中有三类:List,Queue,Set 其中List,有三个子实现类:ArrayList,Vecto ...
- ArrayList源码解析
ArrayList简介 ArrayList定义 1 public class ArrayList<E> extends AbstractList<E> implements L ...
- ArrayList源码剖析
ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...
- 转:【Java集合源码剖析】ArrayList源码剖析
转载请注明出处:http://blog.csdn.net/ns_code/article/details/35568011 本篇博文参加了CSDN博文大赛,如果您觉得这篇博文不错,希望您能帮我投一 ...
- 顺序线性表 ---- ArrayList 源码解析及实现原理分析
原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7738888.html ------------------------------------ ...
- ArrayList 源码(基于Java1.8)
ArrayList 源码 ArrayList 基于数组实现,也就是类对变量 Object[]系列操作,封装为常用的add,remove,indexOf, contains本质是通过 size 计数器对 ...
- 【java提高】---ArrayList源码
ArrayList源码 一.定义 public class ArrayList<E> extends AbstractList<E> implements List<E& ...
- Java集合干货——ArrayList源码分析
ArrayList源码分析 前言 在之前的文章中我们提到过ArrayList,ArrayList可以说是每一个学java的人使用最多最熟练的集合了,但是知其然不知其所以然.关于ArrayList的具体 ...
随机推荐
- MVC在VIEW中动态控制htmlAttributes的方法
@{ IDictionary<string, object> dic = new Dictionary<string, object>(); dic.Add("cla ...
- 【CC2530入门教程-01】IAR集成开发环境的建立与项目开发流程
[引言] 本系列教程就有关CC2530单片机应用入门基础的实训案例进行分析,主要包括以下6部分的内容:1.CC2530单片机开发入门.2.通用I/O端口的输入和输出.3.外部中断初步应用.4.定时/计 ...
- Discuz开发帮助
http://ishare.iask.sina.com.cn/f/33819255.html Discuz二次开发手册(下载) http://dev.discuz.org/wiki/index.php ...
- 关于MATLAB处理大数据坐标文件2017527
第一次提交数据: 今天用了8个特征,加上的这一个特征是 从3000条测试数据中测试失败的数据总结出来的树的数目为50再次使用3000条测试数据测试结果-- 结果不错: 99%但是运行官网数据结果分数- ...
- RabbitMQ系列教程之四:路由(Routing)
(使用Net客户端)在上一个教程中,我们构建了一个简单的日志系统,我们能够向许多消息接受者广播发送日志消息.在本教程中,我们将为其添加一项功能 ,这个功能是我们将只订阅消息的一个子集成为可能. 例如, ...
- php通过curl扩展进行模拟登录(含验证码)
以下为本人工作中遇到的需要做的事情,之前也没怎么用过curl,查了好多资料,才稍微弄明白一点:本文所有内容只是自己平日工作的记录,仅供大家参考:<?php/*** 模拟登录*/header(&q ...
- JAVA基础——变量和常量
JAVA的变量和常量知识总结 一.认识java标识符 标识符就是用于给 Java 程序中变量.类.方法等命名的符号. 使用标识符时,需要遵守几条规则: 1. 标识符可以由字母.数字.下划线(_).美 ...
- Python对象类型及其运算
Python对象类型及其运算 基本要点: 程序中储存的所有数据都是对象(可变对象:值可以修改 不可变对象:值不可修改) 每个对象都有一个身份.一个类型.一个值 例: >>> a1 = ...
- window.getSelection和document.selection
window.getSelection和document.selection IE9以下支持:document.selection IE9.Firefox.Safari.Chrome和Opera支 ...
- Linux 下挂在ntfs 硬盘
CentOS 7 下想要挂载NTFS的文件系统该怎么办呢? 我们需要一个NTFS-3G工具,并编译它之后在mount就可以了,就这么简单. 首先要进入官网下载NTFS-3G工具 http://www. ...