摘要

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——源码探究的更多相关文章

  1. 竟然还有人说ArrayList是2倍扩容,今天带你手撕ArrayList源码

    ArrayList是我们开发中最常用到的集合,但是很多人对它的源码并不了解,导致面试时,面试官问的稍微深入的问题,就无法作答,今天我们一起来探究一下ArrayList源码. 1. 简介 ArrayLi ...

  2. java8 ArrayList源码阅读

    转载自 java8 ArrayList源码阅读 本文基于jdk1.8 JavaCollection库中有三类:List,Queue,Set 其中List,有三个子实现类:ArrayList,Vecto ...

  3. ArrayList源码解析

    ArrayList简介 ArrayList定义 1 public class ArrayList<E> extends AbstractList<E> implements L ...

  4. ArrayList源码剖析

    ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...

  5. 转:【Java集合源码剖析】ArrayList源码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/35568011   本篇博文参加了CSDN博文大赛,如果您觉得这篇博文不错,希望您能帮我投一 ...

  6. 顺序线性表 ---- ArrayList 源码解析及实现原理分析

    原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7738888.html ------------------------------------ ...

  7. ArrayList 源码(基于Java1.8)

    ArrayList 源码 ArrayList 基于数组实现,也就是类对变量 Object[]系列操作,封装为常用的add,remove,indexOf, contains本质是通过 size 计数器对 ...

  8. 【java提高】---ArrayList源码

    ArrayList源码 一.定义 public class ArrayList<E> extends AbstractList<E> implements List<E& ...

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

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

随机推荐

  1. 常见浏览器的宽高代码写法!有原生JavaScript和jquery两种写法-------------------------------以及我的个人网站

    我的个人网站 点击链接!欢迎大家访问 下面是网页一些常见的宽高的获取.........这是原生的写法(JavaScript) 网页可见区域宽: document.body.clientWidth 网页 ...

  2. css让文字在一行内显示

    1.例如 p元素,里面的文字不换行显示,超出部分不隐藏 p{ width:100px; word-break:keep-all; white-space:nowrap; } 2.例如 p元素,里面的文 ...

  3. 用 Google 挖掘赚钱思路

    为程序员,如果学了一堆技术却没有用武之地,实在可惜,如何把自己积累的技术利用起来?通俗一点,程序员有哪些赚钱的门路? 比较常见的一种方式是接私活,不过私活的复杂度不一,沟通成本会非常高,另一方面,私活 ...

  4. XCOM2中敌对生物设计分析(Aliens篇)

    Aliens Aliens作为游戏设定中入侵的外星人,有各式外貌及奇特的战斗方式,掌握一些高能科技或利用精神力量进行攻击 Sectoid 使用灵能战斗的外星人,并无高级版本,初级便会使用精神控制,生命 ...

  5. C#中的Dictionary的使用

    在工作中有时候会用到Dictionary,由于自己之前没用过,参考了一下前人和先辈的实践,仿照着写了一个Test,第一次用还不是很熟练,要多实践练习才能用的得心应手,写代码重在敲键盘,以此为诫.(主要 ...

  6. Spring Security -SpEL表达式

    Spring Security -SpEL表达式 开启SpEL表达式 <!-- use-expressions是否开启 SpEL表达式 o.s.s.web.access.expression.W ...

  7. JS运动框架的封装过程(一)

    给大家出一道题,从起点A走到目的地B,一共用了1000毫秒,每一次是30毫秒,请问你在这里面得到了哪些信息? 信息有哪些呢? 第一个,总时长是:1000毫秒 第二个,多久时间走一次?30毫秒 第三个, ...

  8. Java Web - HTML 常用标签和符号

    1.Html 注释,pre,&lt,&gt,&nbsp,超级链接,marquee,img标签 <html> <head> <title>常用 ...

  9. iconfont字体图标的使用方法--超简单!

    我之前因为项目用bootstrap比较多,所以使用font awesome字体图标比较多,后来接触到了iconfont,发现想要的什么图标都有,还可以自定义图标,非常强大!之前看了一波教程,觉得繁琐, ...

  10. CentOS7 yum安装zabbix3.2.6

    前言: 本人小白,在一个多月前通过面试进入公司,在进入公司的第一天,老板把我叫到他办公室,坐下来慢慢喝茶,吹牛,给我吹他们以前做的软件,经营的产品,还装作一副什么都告诉我的样子,其实这都是套路,我早已 ...