定义
ArrayList实际上是一个动态数组,容量可以动态增长
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- RandomAccess接口,标记接口,被List实现之后,表明List提供了随机访问功能,也就是通过下标获取元素对象的功能(使用for循环的方式获取数据会优于用迭代器获取数据)。而LinkedList(基于链表实现)没有实现该接口,使用Iterator来迭代(顺序下标访问)速度更快(for循环查找一个元素时间复杂度为O(n))
- 实现Cloneable接口,即覆盖了函数clone(),能被克隆
- 实现Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输
Iterator 主要性能开销在next方法体,其一:对记录集进行检测,如果在迭代的过程中,记录集有被修改,会抛出异常;其二:next方法体内有try…catch方法体,这也会影响性能,JVM不能对try…catch块内的代码进行优化。
而for因为不管数据被中途修改,也不进行异常处理,所以当然快啦
成员变量
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;
// 空对象数组,**当用户指定容量为0时返回这个**,则 elementData 指向空数组对象 EMPTY_ELEMENTDATA
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认空对象数组,**当用户调用无参构造函数,返回的是该数组**
// 当用户第一次添加元素时,该数组将会扩容,变成默认容量为10的一个数组(通过ensure CapacityInternal()实现)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存放List元素的数组缓冲区,elementData是个动态数组
// 该值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时,当第一次添加元素进入 ArrayList 中时,数组将扩容值 DEFAULT_CAPACITY(10)
// 当前对象不参与序列化
transient Object[] elementData;
// 实际元素大小,默认为0
private int size;
// 最大数组容量 MAX_VALUE = 0x7fffffff
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 2147483639
}
为什么elementData用transient修饰?
因为elementData里面不是所有的元素都有数据,因为容量问题,elementData里面一些元素是空的,它们没有必要序列化。所以需要手动的序列化数组对象(使用writeObject和readObject),使用了transient来禁止自动序列化这个数组。
序列化
构造函数
/**
* 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;
}
/**
* 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
*/
// 传入collection
public ArrayList(Collection<? extends E> c) {
// 将collection对象转换为数组,将数组的地址赋给elementData
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
// 如果不是Object类型的数组
if (elementData.getClass() != Object[].class)
// 把elementData复制到Object类型的数组
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
增加
public boolean add(E e) {
// 确保数组已使用长度加1之后足够存下下一个数据①
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
// 判断index是否越界,错误产生IndexOutOfBoundsException
rangeCheckForAdd(index);
// 进行扩容检查
ensureCapacityInternal(size + 1); // Increments modCount!!
// 对数组进行复制,将index后的所有数据后移一个位置
// 将指定源数组中的数组从指定位置复制到目标数组的指定位置
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
// 容量+1
size++;
}
// 检查插入的位置是否在数组容量范围内
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
扩容机制
ensureCapacityInternal()
private void ensureCapacityInternal(int minCapacity) {
// 如果elementData为DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组时
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 数组空间至少需要扩容到DEFAULT_CAPACITY(10)
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// minCapacity最小容量
ensureExplicitCapacity(minCapacity);
}
ensureExplicitCapacity()
private void ensureExplicitCapacity(int minCapacity) {
// 记录修改的次数(即每次add或者remove他的值都会加1)
modCount++;
// overflow-conscious code
// 当前数组的大小和minCapacity进行比较
if (minCapacity - elementData.length > 0)
// 进行扩容
grow(minCapacity);
}
grow()
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// oldCapacity >> 1的含义是:将oldCapacity的值除以2
// 新的容量是原来的1.5倍(右移不会有小数)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新的容量依然达不到最小空间要求,则直接将空间扩大到minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果扩容后的数组空间超出了最大容量限制,调用hugeCapacity()进行大容量分配
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 构建符合容量大小的数组并复制原数组的数据
elementData = Arrays.copyOf(elementData, newCapacity);
}
hugeCapacity()
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// minCapacity > 2147483639 ? 2147483647(2^31-1) : 2147483639
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
删除
// 移除指定索引处的元素值,并返回该值
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
// 待删除的元素值
E oldValue = (E) elementData[index];
// 需要移动的元素的数量
int numMoved = size - index - 1;
// 如果移除元素在数组最后一位,numMoved = 0
// 只在numMoved>0才需要移动
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 最后一位置为null
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
// 移除集合中包含第一位元素值为o的对象
// 如果包含该对象返回true,否则返回false
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 E set(int index, E element) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}
查找
// 返回集合中包含第一位元素值为o的对象的下标值
public int indexOf(Object o) {
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;
}
// 逆序查找,返回最后一位
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;
}
每当集合的结构发生变化时,modCount就会递增,当在对集合进行迭代操作时,迭代器会检查此参数值,如果检查到此参数的值发生变化,就说明在迭代的过程中集合的结构发生了变化,此时迭代的元素可能就并不是最新的了,因此会直接抛出异常。
缩容
往 ArrayList 插入大量元素后,又删除很多元素,此时底层数组会空闲出大量的空间.因为 ArrayList 没有自动缩容机制,导致底层数组大量的空闲空间不能被释放,造成浪费。
// 将数组容量缩小至元素数量
public void trimToSize() {
modCount++;
if (size < elementData.length) {
// Arrays.copyOf底层也是调用System.arraycopy
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
关于遍历时删除
foreach是个语法糖,编译成字节码后会被转成用迭代器遍历的方式。
List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
for (String temp : a) {
System.out.println(temp);
if("1".equals(temp)){
a.remove(temp);
}
}
// 等价于上面的
List<String> a = new ArrayList<>();
a.add("1");
a.add("2");
Iterator<String> it = a.iterator();
while (it.hasNext()) {
String temp = it.next();
System.out.println("temp: " + temp);
if("1".equals(temp)){
a.remove(temp);
}
}
这样子进行删除操作是不会出现问题的,然而当我们把删除的判断条件换成2后就会报异常。**对倒数第二个元素进行删除不会报异常,而对其他位置的删除会报异常(cursor == size)**实际上 modCount 的数值也增加了 1,只不过循环没发执行到那里,所以异常也就不会被抛出来了。
迭代器源码:
//返回集合迭代器
public Iterator<E> iterator() {
return new 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
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
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];
}
//移除 lastRet 位置的元素,主动更新expectedModCount避免抛异常
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
//因为 lastRet 位置原始的元素被移除了,所以此时 lastRet 指向的元素是原先 lastRet+1 位置的元素
cursor = lastRet;
lastRet = -1;
//因为是 Itr 主动对集合进行修改,所以此处需要主动更新 expectedModCount 值,避免之后抛出异常
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
// 省略不相关的代码
}
第一次进入while循环一切正常,当元素1被删除后,无法再进入while循环,此时it.hasNext()为false(因为cursor = size = 1),没抛异常的原因是循环提前结束,导致next方法没有机会抛异常。
我们要避免上面的做法,不要直接删除,而是使用迭代器的删除方法。
而在利用 for 下标进行遍历的时候,并不会触发 checkForComodification() 方法,所以此时只要要删除的位置比列表大小小时都不会出错。
“快速失败”即fail-fast,它是java集合的一种错误检测机制。当多钱程对集合进行结构上的改变或者集合在迭代元素时直接调用自身方法改变集合结构而没有通知迭代器时,有可能会触发fast-fail机制并抛出异常。有可能触发fast-fail机制而不是肯定。
快速失败
CopyOnWriteArrayList
在遍历过程中,修改modCount值的地方全部加上synchronized?
ArrayList线程不安全
在多个线程进行add操作时可能会导致elementData数组越界。
add方法两步操作:
- 判断是否需要对elementData进行扩容
- 在elementData对应位置上设置值
这样也就出现了第一个导致线程不安全的隐患,在多个线程进行add操作时可能会导致elementData数组越界:(在不扩容的边界)
- 列表大小为9,即size=9
- 线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断
- 线程B此时也进入add方法,它获取到size的值也为9,也开始调用ensureCapacityInternal方法
- 线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。
- 线程B也发现需求大小为10,也可以容纳,返回。
- 线程A开始进行设置值操作, elementData[size++] = e线程A开始进行设置值操作, elementData[size++] = e操作。此时size变为10。
- 线程B也开始进行设置值操作,它尝试设置elementData[10] =线程B也开始进行设置值操作,它尝试设置elementData[10] =e,而elementData没有进行过扩容,它的下标最大为9。于是此时会报出一个数组越界的异常ArrayIndexOutOfBoundsException.
第二步的 elementData[size++] = e设置值的操作同样会导致线程不安全。这步操作也不是一个原子操作,它由如下两步操作构成:
- elementData[size] = e
- size = size + 1
在多线程环境下执行时,可能就会发生一个线程的值覆盖另一个线程添加的值,具体步骤如下:
- 列表大小为0,即size=0
- 线程A开始添加一个元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。
- 接着线程B刚好也要开始添加一个值为B的元素,且走到了第一步操作。此时线程B获取到size的值依然为0,于是它将B也放在了elementData下标为0的位置上。
- 线程A开始将size的值增加为1
- 线程B开始将size的值增加为2
这样线程AB执行完毕后,理想中情况为size为2,elementData下标0的位置为A,下标1的位置为B。而实际情况变成了size为2,elementData下标为0的位置变成了B,下标1的位置上什么都没有。之后添加元素会从下标为2的位置上开始。
如何让ArrayList线程安全
- 给list的方法加上synchronized
- Collections.synchronizedList(new ArrayList())
- CopyOnWriteArrayList
- ThreadLocal
- 简单介绍下ArrayList
ArrayList是以数组实现,可以自动扩容的动态数组,当超出限制的时候会增加50%的容量,用System.arraycopy()把原数据复制到新的数组,因此最好能给出数组大小的预估值。默认第一次插入元素时创建大小为10的数组。arrayList的性能很高效,不论是查询和取值很迅速,但是插入和删除性能较差,该集合线程不安全。因为它实现了RandomAccess接口,最好用for循环遍历数据;用迭代器遍历集合的时候,如果自身进行集合的修改或者多线程下修改就会抛异常- ArrayList的自动扩容是怎么实现的?
每次在add()一个元素时,arraylist都需要对这个list的容量进行一个判断。如果容量够,直接添加,否则需要进行扩容。在1.8 arraylist这个类中,扩容调用的是grow()方法。
在核心grow方法里面,首先获取数组原来的长度,然后新增加容量为之前的1.5倍。随后,如果新容量还是不满足需求量,直接把新容量改为需求量,然后再对新容量进行最大化判断。
通过grow()方法中调用的Arrays.copyof()方法进行对原数组的复制,达到扩容的目的。
本文深入解析ArrayList的内部实现,包括动态数组的自动扩容机制、线程安全问题及解决方案、常见操作如增删改查的实现原理,以及遍历和迭代器的正确使用方法。
1044

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



