Java源码解读之ArrayList源码解读
简介
底层是可变长的数组,随着不断添加元素,其容量也会自动增长,ArrayList实现了List的所有可选操作,允许存放null元素。
但是,ArrayList不是线程安全的,如果想获取线程安全的List,可以通过
List list = Collections.synchronizedList(new ArrayList(...));来获取线程同步的List。
我们查看Collections的源码,发现大部分的方法都加上了synchronized同步,只有四个方法没有同步,需要用户自己添加。

比如说调用iterator方法。
List list = Collections.synchronizedList(new ArrayList());
...
synchronized(list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
实现的接口

我们重点关注三个接口RandomAccess``Serializable``Cloneable。这三个接口都很特别,接口内部没有定义任何方法,只是起到一个标记作用。
-
RandomAccess标记其支持快速(通常是固定时间)随机访问。实现此接口的实例使用for循环遍历的速度要快于使用迭代器。
-
Serializable序列化接口没有方法或字段,仅用于标识可序列化的语义。类通过实现
Serializable接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。 -
Cloneable标记
Object.clone()方法可以合法地对该类实例进按字段复制。
如果在没有实现Cloneable接口的实例上调用Object的clone方法,则会导致抛出CloneNotSupportedException异常。实现此接口的类应该使用公共方法重写Object.clone。
成员变量
//默认初始化的容量为10
private static final int DEFAULT_CAPACITY = 10;
//用于共享的空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//用于初始化时没有指定初始化大小的空数组。这个也是共享的空数组实例。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**存储ArrayList元素的数组缓冲区。ArrayList的容量是此数组缓冲区的长度.当数组是空的时候且elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,添加第一个元素,elementData数组会扩容到默认的容量10.
**/
transient Object[] elementData;
//ArrayList中包含数组的个数。
private int size;
构造方法
public ArrayList(int initialCapacity)
/* 构造具有指定初始容量的空列表
* 1.如果指定的初始化容量>0,那么就申请的数组空间。
* 2.如果指定的初始化容量==0,那么elementData=EMPTY_ELEMENTDATA,也就是指向共享的空数组实例。
* 3.如果指定的初始化容量<0,直接抛出异常
*/
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);
}
}
public ArrayList(int initialCapacity)
/* 构造一个初始容量为10的空列表。
* 其实这里还是一个空的数组,因为指向了空数组,
* 当第一次添加元素时,才主动扩容到10,具体可以看add方法的逻辑.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c)
public ArrayList(Collection<? extends E> c) {
//1.将指定集合转换成数组
elementData = c.toArray();
//2.设置size为转换成数组的长度,如果size不为0,其实就是size>0
//
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//这里是一个bug,有些情况下返回的不是object数组
//如果转换的数组不是object数组,那么就创建一个新的object数组,并将元素拷贝进去
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
//如果长度是0,那么就直接是EMPTY_ELEMENTDATA空组数。
this.elementData = EMPTY_ELEMENTDATA;
}
}
其他方法
public int size()
/***
****元素的个数
*/
public int size() {
return size;
}
public boolean isEmpty()
/**
* list是否为空,其实就是判断size是否为0
*/
public boolean isEmpty() {
return size == 0;
}
public boolean contains(Object o)
/**
* 判断是否包含某个元素,只要有一个匹配的元素就可以
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o)
/**
* 返回指定元素在list中的位置,也就是下标或是索引
*/
public int indexOf(Object o) {
//1.如果指定元素是null,那么就遍历比较,返回第一个为null的索引
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//2.如果元素不为null,那么遍历比较,使用equals,返回第一个匹配的索引
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
//3.如果没有匹配上,那么返回-1
return -1;
}
public int lastIndexOf(Object o)
/** 查找最后出现的指定元素的位置。
* 这个就不用多说了,和indexOf对比,indexOf是正序遍历,lastIndexOf是倒序遍历。
*
*/
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;
}
public Object clone()
/**
* 就如同我们开始所说的,实现了Serializable接口,那么就需要重写clone。
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
public E get(int index)
/**
* 获取指定索引的元素
*/
public E get(int index) {
//1.检查是否越界
rangeCheck(index);
//2.返回对应的索引元素,因为是数组,直接通过下标进行定位。
return elementData(index);
}
public E set(int index, E element)
/**
替换指定位置的元素
*/
public E set(int index, E element) {
//1.检查是否越界
rangeCheck(index);
//2.获取旧的元素
E oldValue = elementData(index);
//3.设置指定索引的元素为指定新的元素
elementData[index] = element;
//4.返回旧的元素
return oldValue;
}
public boolean add(E e)
public boolean add(E e) {
//1.检查是否需要扩充及扩容操作,将新的元素个数作为参数传入
ensureCapacityInternal(size + 1); // Increments modCount!!
//2.元素个数size自增1,然后设置自增下标指向新的元素,也就是将新的元素放置到末位。
//3.szie是在这里自增1,用于计数,记录数组中的元素个数。
//并且设置新元素
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//计算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//1.如果是空数组,也就是初始化Arraylist的时候是无参构造,那么第一次添加元素就需要
//初始化容量到默认的容量10,返回10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//2.如果到这里,返回的就是新的元素个数,也就是size+1元素个数
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
//1.用来记录list结构的变动次数
modCount++;
// overflow-conscious code
//2.如果新的元素个数比现在的数组长度要大,那么就需要扩容了。
//2.1如果是默认初始化,这个时候minCapacity为10,elementData为空数组,长度为0,那么
//满足条件需要扩容。
//2.2 如果第一次扩容到10,之后当添加到第11个元素时,minminCapacity为11,elementData为10,那么还需要扩容
//依次类推。
//2.3 如果初始化的时候指定了初始化的容量,比如说是5,那么在添加前五个元素之前,都不会触发扩容,当添加第六个元素时,
//minCapacity是6,elementData.length为5,满足minCapacity - elementData.length > 0的条件,就需要扩容了。
if (minCapacity - elementData.length > 0)
//2.3 开始扩容,传入的参数,是新的元素个数(也是最小的容量要求),除了默认初始化第一次为10,其他都是size+1
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
//1.获取旧的容量,也就是数组长度
int oldCapacity = elementData.length;
//2.设置新的容量为旧的容量的1.5倍,
//旧的容量向右移1位(对于计算机来说移位操作比较快),就是原来的1/2,加上oldCapacity,也就是原来的1.5倍。
int newCapacity = oldCapacity + (oldCapacity >> 1);
//3.如果新的容量还是比最小要求容量小,那么直接设置minCapacity为新容量。
//这个有个疑惑,什么情况下newCapacity会比minCapacity小?
//因为int有最大的表示类型Integer.MAX_VALUE,如果oldCapacity接近Integer.MAX_VALUE,那么新的newCapacity=oldCapacity + (oldCapacity >> 1)
// 计算之后,newCapacity可能会超过Integer.MAX_VALUE,从而导致溢出,那么此时newCapacity就会小于oldCapacity。
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//4.如果newCapacity比MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)还大,也就是newCapacity接近于Integer.MAX_VALUE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//5.拷贝元素
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
//溢出判断,如果minCapacity为Integer.MAX_VALUE+1就会导致溢出
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//这里就有个问题,什么样的情况下才能使minCapacity=MAX_VALUE,
//因为这个方法最外面一层还有一个判断newCapacity - MAX_ARRAY_SIZE > 0,也就是说
//需要满足newCapacity>MAX_ARRAY_SIZE而且minCapacity > MAX_ARRAY_SIZE,这个时候minCapacity才等于Integer.MAX_VALUE。
//是否永远也达不到这个条件???
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
public void add(int index, E element)
/**
*
* 在指定位置添加一个新元素。那么其后位置的所有元素要往后移。
*/
public void add(int index, E element) {
//1.检查是否越界
rangeCheckForAdd(index);
//2.检查是否需要扩容及扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//3.将elementData数组中的index位置后面的(size-index)个元素,拷贝到(index+1)的位置。
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//4.写入新的元素
elementData[index] = element;
//5.元素个数+1
size++;
}
public boolean addAll(int index, Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
//1.指定的集合转换成数组
Object[] a = c.toArray();
//2.要添加的元素个数
int numNew = a.length;
//3.检查是否扩容,如果是,进行扩容操作
ensureCapacityInternal(size + numNew); // Increments modCount
//4.将目标数组中的元素拷贝到elementData的末位
System.arraycopy(a, 0, elementData, size, numNew);
//5.元素个数增加numNew
size += numNew;
//5.如果指定集合不为空,返回成功。
return numNew != 0;
}
public E remove(int index)
/**
* 删除指定位置的元素
*/
public E remove(int index) {
//1.越界判断
rangeCheck(index);
//2.数组结构变化计数+1
modCount++;
//3.取出要删除的元素
E oldValue = elementData(index);
//4.要移动的个数
int numMoved = size - index - 1;
//5.
//如果删除的是数组末尾的元素,那么其实不用移动数组,直接把末位的元素置成null就可以了。
//如果不是末位的元素,那么就需要把目标位置后面的元素集体往前移动一位
//比如说 一个数组 存了 1,2,3,4 四个元素,现在要删除2,那么就把3,4集体往前启动一位,
//移动完成之后变成1,3,4,4,然后再把最后的元素变成null,最终也就是1,3,4.
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//6.最后的元素设置为null,元素size个数-1
elementData[--size] = null; // clear to let GC do its work
//7.返回旧的元素,也就是删除的元素
return oldValue;
}
public boolean remove(Object o)
/**
* 删除指定的元素,匹配到的第一个元素
*/
public boolean remove(Object o) {
//1.如果指定的元素是null
if (o == null) {
//2.遍历比较
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//3.极速删除
fastRemove(index);
return true;
}
} else {
//2.如果指定的元素不为null
for (int index = 0; index < size; index++)
//2.遍历比较
if (o.equals(elementData[index])) {
//3.极速删除
fastRemove(index);
return true;
}
}
return false;
}
public void clear()
/**
*
* 删除所有的元素,也就是清空list
*
*/
public void clear() {
modCount++;
//遍历list,每个元素都设置为null
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
//元素个数为0.
size = 0;
}
public boolean removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
//1.判断目标集合是否是null,是的话抛异常
Objects.requireNonNull(c);
//2.开始批量删除
return batchRemove(c, false);
}
/**
* 这里是一个算法。
*惊艳的算法
*/
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
//elementData为[2,4,6]
//c集合是[1,4]
//complement == false是我们来分析
//第一次循环之后,elementData为变为[2,4,6] w=1,r=1
//第二次循环之后,elementData为变为[2,4,6],w=1,r=2
//第三次循环之后,elementData为变为[2,6,6],w=2,r=3
//那么执行,w!=size,就直接将w之后的元素置空,也就是变成[2,6]
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
总结
ArrayList底层是可变长的数组,默认初始化容量为10,可以自动扩充容量。每次扩充的容量为旧的容量的1.5倍。正是因为数组的特性,如果随机访问数组中的元素,那么访问速度比其他数据结构会快。但是,删除,插入元素会调整元素的位置甚至是重新申请空间,拷贝元素,因此相对链表而言比较慢。
关于扩容算法,扩容之后newCapacity什么情况下会比oldCapacity小,请查看这篇文章。
ArrayList扩容的一点疑惑–什么情况下newCapacity - minCapacity < 0
欢迎大家评论交流,欢迎技术大佬,小白进群讨论:163690707(QQ群)
个人博客:请点击此处。

本文详细探讨了Java中ArrayList的源码,介绍了其作为动态数组的数据结构,非线程安全的特点,以及如何通过Collections.synchronizedList获得线程安全的ArrayList。文章还重点讲解了ArrayList实现的Serializable、Cloneable和List接口,成员变量,构造方法和其他关键操作。最后,总结了ArrayList的优缺点,包括快速随机访问和插入删除的效率,并提及了扩容算法及其特殊情况。
538

被折叠的 条评论
为什么被折叠?



