文章目录
ArrayList的简介
1,ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacityXXX 方法来操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。
2,它继承了AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。
3,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1)
4,继承AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
5,实现RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。对于实现了RandomAccess 接口的集合在使用Collections.binarySearch方法,的执行策略有所不同。
6,实现Cloneable 接口,即覆盖了函数 clone(),能被克隆。
7,实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。
8,和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。
9,ArrayList的图解如下。
源码分析
首先我们先来看看ArrayList集合的相关属性和构造方法
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8683452581122892189L;
/**
* 设置默认容量为10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 如果我们使用的是ArrayList(int initialCapacity)来创建ArrayList数组时,
* 如果我们initialCapacity等于0那么直接用EMPTY_ELEMENTDATA,
* 如果initialCapacity大于0那么this.elementData = new Object[initialCapacity];
* 空数组(用于空实例)。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};//来初始化我们的elementData
/**
* 如果我们使用的是ArrayList() 的无参构造方法,那么将使用
* DEFAULTCAPACITY_EMPTY_ELEMENTDATA来初始化我们的elementData
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 用来存放集合中的元素
*/
transient Object[] elementData;// non-private to simplify nested class access
/**
* 集合中容纳元素的个数
*/
private int size;
/**
* 有参构造方法,我们可以在初始化的时候自定义elementData数组的长度
* @param initialCapacity 表示初始容量
*/
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() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 有参构造方法,参数是集合
*/
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;
}
}
//。。。。。。
}
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++;
}
ArrayList的扩容机制
我们要知道,ArrayList的扩容不是每次扩容只扩容一次,而是会扩容很多的空间。
通过add方法我们可以知道会调用了ensureCapacityInternal方法,这个就是在调用扩容机制。
ensureCapacityInternal(int minCapacity)的作用:得到最小扩容量
首先我们要知道ArrayList的扩容发生在添加元素阶段,比如我们要在x位置上添加元素,这个位置可能是集合尾部,集合中部或者集合前面,不管添加元素的位置在哪里,肯定是扩容数组的尾部。那么我们就要判断一下,需要扩容多少。
所以add方法调用ensureCapacityInternal(int minCapacity)方法,并传入参数size+1。
执行if语句,如果我们的的判断的结果是true,那么证明这是我们用无参构造方法ArrayList()生成的,所以得到最小的扩容量是DEFAULT_CAPACITY(默认值是10),则minCapacity=10。
如果返回的是false,证明elementData不是空数组,则minCapacity=size+1。
/**
* 得到最小扩容量
*/
private void ensureCapacityInternal(int minCapacity) {//minCapacity,为最小扩容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);//传入最小扩容量
}
然后我们可以看到调用ensureExplicitCapacity(minCapacity)方法。
ensureExplicitCapacity(minCapacity)的作用:明确扩容量
首先,我们看到这个方法中执行了modCount++操作,这个东西很重要,我们在后面进行讲解。
执行if语句,如果我们判断结果是true,就证明我们真的需要扩容了。
如果返回false,证明不需要进行扩容,现在的数组长度,可以容纳这次添加元素
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
//检查数组的容量是否足够
if (minCapacity - elementData.length > 0)
grow(minCapacity);//真正扩容的方法
}
执行真正的扩容方法grow(minCapacity)
/*
* 要分配的最大数组大小
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
//旧容量
int oldCapacity = elementData.length;
/*
新容量,在这个用的是位运算符,在这里呢,顺便提一提位运算符
<< : 左移运算符,num << 1,相当于num乘以2
>> : 右移运算符,num >> 1,相当于num除以2
>>> : 无符号右移,忽略符号位,空位都以0补齐
A >>> B -- B 指定要移位值A 移动的位数。
无符号右移的规则只记住一点:忽略了符号位扩展,0补最高位
位运算的执行效率是比普通的乘除要高的
所以说在这里呢,新容量更新为旧容量的1.5倍,
*/
int newCapacity = oldCapacity + (oldCapacity >> 1);
//然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量
//这个时候,有的人可能会想,不是已经得到了新容量,为什么还有与minCapacity最小容量进行比较呢?
//这是因为,在ArrayList的方法中我们其实可以直接自己任命最小容量的值,
//调用ensureCapacity()方法,这个方法的具体细节,现在不讲。
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//再检查新容量是否超出了ArrayList所定义的最大容量,
//若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
//如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,
//否则,新容量大小则为 MAX_ARRAY_SIZE。
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//执行Arrays.copyOf方法,数组的拷贝复制
elementData = Arrays.copyOf(elementData, newCapacity);
}
//比较minCapacity和 MAX_ARRAY_SIZE
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(E e) 方法的扩容
1,检查数组的容量是否足够。
2,够,直接添加元素,不够,扩容到1.5倍。
3,第一次扩容后,如果还是小于minCapacity,就将容量扩充为minCapacity。
4,然后添加元素。
接着我们看一看add(int index, E element)方法
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++;
}
//判断角标是否合法
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
自定义扩容大小ensureCapacity方法
//这个方法我们可以直接调用,可以自定义扩容大小
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);
}
}
get方法
public E get(int index) {
rangeCheck(index);//判断角标是否合法
return elementData(index);//直接返回结果
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
set方法
public E set(int index, E element) {
rangeCheck(index);//判断角标是否合法
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
remove方法:①按角标删除②按元素删除
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
//elementData:源数组
//index + 1:源数组中的起始位置
//elementData:目标数组
//index:目标数组中的起始位置
//numMoved:要复制的数组元素的数量;
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
}
ArrayList中modCount的作用
在ArrayList中有个成员变量modCount,继承于AbstractList。
这个成员变量记录着集合的修改次数,也就每次add或者remove它的值都会加1。这到底有什么用呢?
import java.util.ArrayList;
import java.util.Iterator;
public class Demo {
public static void main(String args[]){
ArrayList list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
Iterator iterator = list.iterator();
while(iterator.hasNext()){
Integer number = (Integer) iterator.next();
if(number == 3){
list.remove(number);
}
System.out.println(number);
}
}
}
我可以告诉大家这个代码会抛异常,ConcurrentModificationException
为什么,因为ArrayList被设计成非同步的,不能在遍历的时候进行修改和删除,这个就与modCount有关。
//执行下面的代码
Iterator iterator = list.iterator();
public Iterator<E> iterator() {
return new Itr();
}
//接着就会调用ArrayList内部实现的Iterator接口的类Itr
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
//会将自己的ArrayList里面的modCount复制给内部类中的expectedModCount
int expectedModCount = modCount;
//调用遍历函数
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
//在这里面我们可以看到一个checkForComodification();
//在这个方法中,if语句里面modCount != expectedModCount如果不等会抛异常。
//我们可以知道如果我们在遍历的时候调用ArrayList的add或者remove方法,
//会导致执行modCount++操作,从而导致异常
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ArrayList中RandomAccess 接口的作用
查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能
这个接口的作用要在调用Collections.binarySearch() 才有用。
在这里我们可以看到ArrayList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
//实现了RandomAccess接口
private static <T>
int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
int low = 0;
int high = list.size()-1;
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = list.get(mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
//没有实现
private static <T>
int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
{
int low = 0;
int high = list.size()-1;
ListIterator<? extends Comparable<? super T>> i = list.listIterator();
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = get(i, mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
list 的遍历方式选择:
实现RandomAccess 接口的list,其实可以看做是使用了for循环。
没有实现 RandomAccess 接口的list,其实调用的是iterator遍历。