前言
前面都是分析了一些接口或抽象类。是不是感觉很枯燥?那么今天就来看看十分常用的ArrayList,看看它到底是如何来实现前面说的那些接口或者抽象类的。
ArrayList
数据结构
我们说一个集合,不得不提的就是他存储的数据结构到底是怎样的。
ArrayList,通过名字就能看出,是Array + List,虽然不能等同,但是能从这方便去思考。
ArrayList底层就是数组一个数组结构,但是他是可以扩容的,不像数组一样大小固定。
好,言归正传,还是一起来分析源码吧~~
整体介绍
ArrayList继承了AbstractList,实现了List,RandomAccess,Cloneable,Serializable接口。
ArrayList是对List的一个可调整大小的实现类,实现了List所有的可选操作,而且允许所有元素的存储,包括NULL值。
然后从时间复杂度上来看,size,isEmpty,get,set,iterator和listIterator操作方法的时间复杂度都为O(1),而add操作的时间复杂度为O(n);其他的操作粗略的来看都需要线性的时间,具体看源码再分析。
属性
ArrayList有7个属性,一个一个来看。
1、serialVersionUID
不多说,序列化和反序列化的标识。
2、DEFAULT_CAPACITY
/**
* 默认的初始化大小
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
3、EMPTY_ELEMENTDATA
/**
* 用于空实例的共享空数组实例。
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
4、EFAULTCAPACITY_EMPTY_ELEMENTDATA
/**
* 用于默认大小的空实例的共享空数据实例。
* 这个区别于EMPTY_ELEMENTDATA,是因为想知道当首个元素被加入的时候,需要扩大多少。
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
5、elementData
/**
* 数组缓冲到ArrayList元素存储的地方。
* ArrayList的容量就是这个数组缓冲区的长度。
* 任何elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList会在第一个元素被添加的时候自动扩展到DEFAULT_CAPACITY(默认容量)
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
6、size
/**
* ArrayList的大小,即包含的元素的个数
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
7、MAX_ARRAY_SIZE
/**
* 数组允许的最大尺寸
* The maximum size of array to allocate.
* 一些虚拟机会在数组里保留一些头信息,所以需要预留一部分空间来存放这些头信息。
* Some VMs reserve some header words in an array.
* 尝试分配一个更大的数组可能导致OutOfMemoryError错误。
* 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;
方法
构造函数
/**
* 指定初始容量的空list的构造器
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造一个list,包含了指定的集合的所有元素,按照指定集合的迭代器返回的顺序。
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
// 将元素通过数组形式复制到目标list的数组缓冲区中
elementData = c.toArray();
if ((size = elementData.length) != 0) { // 判断数组大小是否为0
// c.toArray might (incorrectly) not return Object[] (see 6260652)
// c.toArray 可能返回的不是一个Object对象的数组,这时要通过Arrays.copyOf方法来创建一个Object数组。
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
这里有一点不是很明白,就是第三个构造函数中,调用了c.toArray()方法后,为什么下面还要考虑是不是Object对象数组,c.toArray()方法不是本身返回的就是Object对象数组吗?暂时没弄明白。
容量操作相关
1、public void trimToSize()
释放没有被元素占用的那些空余的空间,一般应该是确定了不会再有新增元素之后,再调用比较好,不然一旦有新增操作,还要扩容,反而影响性能。主要看是要空间还是要性能。
/**
* 修剪ArrayList实例的容量,改为与当前实际元素大小相等,避免空间浪费。通过调用该方法,能最小化ArrayList实例占用的空间
* Trims the capacity of this <tt>ArrayList</tt> instance to be the
* list's current size. An application can use this operation to minimize
* the storage of an <tt>ArrayList</tt> instance.
*/
public void trimToSize() {
// 对结构的一种操作,需要检查并发
modCount++;
// 如果元素个数比数组空间小,则要将元素按size尺寸,拷贝到一个新的数组里,释放多余空间。
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
2、public void ensureCapacity(int minCapacity)
主动扩容操作,可以手动调用该方法来达到扩容的目的
/**
* 扩大ArrayList实例的容量,如果必要,至少要能承受住minCapacity参数指定的元素个数
* Increases the capacity of this <tt>ArrayList</tt> instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
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;
// 需要的容量比默认的大时,需要扩容
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private void ensureExplicitCapacity(int minCapacity) {
// 扩容是改变底层结构的操作,需要修改modCount,检查并发
modCount++;
// overflow-conscious code
// 指定的扩容容量比当前元素个数大时。
if (minCapacity - elementData.length > 0)
// 扩容操作
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
// 保存旧的数组的长度
int oldCapacity = elementData.length;
// 获得新的容量 = oldCapacity*1.5 ,就是扩容为原来的1.5倍,这里用了位操作,提高性能。
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; // 如果原来的基础上扩容1.5倍,还是小于给定的容量,就取给定的容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity); // 如果新的容量超过了最大容量,则申请更大容量
// minCapacity is usually close to size, so this is a win:
// 一般来说,minCapacity是程序员根据元素个数来定的,比较接近于实际的元素个数,而使用newCapacity则会更宽松,多余空间更多。
// 比如原来容量为10,用户申请了容量为12,但是实际上容量是会扩容到15的,而不是12。
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 当所需容量大于最大允许容量时,可以申请Integer.MAX_VALUE的容量。
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
一个扩容操作,竟然涉及到这么多,需要考虑的方方面面还是很多的。
ensureCapacity方法是供用户来调用的,但实际的扩容操作是在最后一个grow方法里的。
这里有一点需要特别注意,用户主动调用ensureCapacity方法来进行扩容时,并不是指定了扩容多少就会扩容多少的,这要看实际执行扩容的grow方法。
举个测试栗子:
public class TestEnsureCapacity {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
System.out.println(list.size());
for (int i = 0; i < 8; i++) {
list.add("aaa");
}
System.out.println(list.size());
list.ensureCapacity(13);
System.out.println(list.size());
}
}
这里要执行断点调试才能看出效果,因为size返回的只是元素实际个数,看不出容量大小。
看到,还没扩容前,是默认容量10,元素个数为8。
扩容后,可以看到,数组容量变为了15,而不是我们指定的13。
再来看如果指定的容量大于旧容量的1.5倍,会怎么样呢?
可以看到,这里就是我们指定多少就是多少了。
数据操作相关
1、拷贝操作
/**
* 返回对Arraylist实例的一个浅拷贝(元素本身不被拷贝,即两个list同个位置的元素指向同一个地址)
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
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);
}
}
所谓浅拷贝,这里给个测试栗子:
package com.test.arraylist;
import java.util.ArrayList;
public class TestClone {
public static void main(String[] args) {
ArrayList<Person> a = new ArrayList<>();
Person p1 = new Person("xiaoming", 1);
Person p2 = new Person("liming", 2);
a.add(p1);
a.add(p2);
System.out.println("没操作的a");
for (Person person : a) {
System.out.println(person);
}
ArrayList<Person> b = (ArrayList<Person>) a.clone();
System.out.println("拷贝后的b");
for (Person person : b) {
System.out.println(person);
}
for (Person person : b) {
if(person.getAge() == 1){
person.setAge(444);
}
}
System.out.println("修改后的b");
for (Person person : b) {
System.out.println(person);
}
System.out.println("没操作的a");
for (Person person : a) {
System.out.println(person);
}
}
}
class Person{
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return this.name + "--" +this.age;
}
}
结果如下:
所以可以看到,b中的Person对象实际上就是a中的对象,两个指向的对象地址是一致的,改变了其中一个,另一个中也会改变。
2、转换为数组
/**
* 返回一个包含了list中的所有元素的数组,并且顺序一致。
* Returns an array containing all of the elements in this list
* in proper sequence (from first to last element).
*
* 返回的数组与原来的list无关。换言之,该方法必须新分配一个新的数组。这样调用者就可以放心地修改返回的数组了。
* <p>The returned array will be "safe" in that no references to it are
* maintained by this list. (In other words, this method must allocate
* a new array). The caller is thus free to modify the returned array.
*
* <p>This method acts as bridge between array-based and collection-based
* APIs.
*
* @return an array containing all of the elements in this list in
* proper sequence
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
/**
* Returns an array containing all of the elements in this list in proper
* sequence (from first to last element); the runtime type of the returned
* array is that of the specified array. If the list fits in the
* specified array, it is returned therein. Otherwise, a new array is
* allocated with the runtime type of the specified array and the size of
* this list.
*
* <p>If the list fits in the specified array with room to spare
* (i.e., the array has more elements than the list), the element in
* the array immediately following the end of the collection is set to
* <tt>null</tt>. (This is useful in determining the length of the
* list <i>only</i> if the caller knows that the list does not contain
* any null elements.)
*
* @param a the array into which the elements of the list are to
* be stored, if it is big enough; otherwise, a new array of the
* same runtime type is allocated for this purpose.
* @return an array containing the elements of the list
* @throws ArrayStoreException if the runtime type of the specified array
* is not a supertype of the runtime type of every element in
* this list
* @throws NullPointerException if the specified array is null
*/
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size) // 如果给定的数据长度小于实际元素个数,则需要新建数组。
// Make a new array of a's runtime type, but my contents:
// 创建一个运行时类的新数组,尺寸按照实际元素个数,这里调用了Arrays.copyOf方法。
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
// 如果给定的数组在填充数据后,有多余空间,则在紧跟集合结束的那个数组元素后面位置设置为null,
// 这是在知晓其他元素都不可能为null的情况下,可以用来标识元素实际个数的长度。
a[size] = null;
return a;
}
3、增删改查一系列操作
增
- add(E e)
/**
* 在list的末尾追加元素
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
// 容量确认,如果不够则扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 一开始数组是空的,容量为0,直到第一次添加元素,才会进行容量初始化,其实也可以看做边界检查的一种。从一个元素也没有到有元素的质变
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
// 先通过calculateCapacity方法计算容量
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
- add(Index, E)
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 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
}
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
查
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
改
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
内部类
内部实现了两个迭代器Itr和ListItr,和一个SubList,功能上来说,可我们前面介绍的AbstractList差不多,没实现什么特别的功能。
java 8 新增的内容
实现了一个静态类ArrayListSpliterator