ArrayList源码
一、前言
ArrayList在日常的开发中使用频率非常高,但是JDK是如何去设计ArrayList的,这就需要我们好好去了解底层实现原理,这样使用起来才能做到心中有数;当然,还能应付面试。本篇文章会围绕ArrayList的核心方法进行源码解读,希望对你有所帮助。
二、提取官方文档核心技术点
- ArrayList是由
可调整大小数组实现
。并允许添加所有元素,包括 null。 - ArrayList实现 List 接口之外,自己还提供了操作内部用于存储列表的数组大小的方法。(此类大致等同于 Vector,只是它是不同步的)
- size、isEmpty、get、set、iterator 和 listIterator 操作在常量时间内运行。 添加操作在摊销常量时间内运行,即添加 n 个元素需要 O(n) 时间。所有其他操作都以线性时间运行(粗略地说)
- 每个 ArrayList 实例都有一个 容量。容量是用于在列表中存储元素的数组的大小。它始终至少与列表大小一样大。当元素添加到 ArrayList 时,其容量会自动增长。除了添加元素具有恒定的摊销时间成本这一事实之外,没有指定增长策略的详细信息。
- 应用程序可以在
使用 ensureCapacity 操作添加大量元素之前增加 ArrayList 实例的容量
。这可能会减少增量重新分配的数量
。请注意,此实现不同步。 - 如果
多个线程同时访问 ArrayList 实例
,并且至少有一个线程在结构上修改
了该列表, 则必须在外部同步该列表。(结构修改指的是添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅设置元素的值不是结构修改。这通常是通过在自然封装列表的某个对象上进行同步来实现的。如果不存在此类对象,则应使用该方法**“ Collections.synchronizedList 包装”**列表。最好在创建时执行此操作,以防止意外地对列表进行不同步访问:
List list = Collections.synchronizedList(new ArrayList(…)); - 此类返回的迭代器 iterator 并且 listIterator 方法是 快速失败的:如果在创建迭代器后的任何时间对列表进行结构修改,则除了通过迭代器自己的 remove OR add 方法之外,迭代器将抛出 ConcurrentModificationException.因此,面对并发修改,迭代器会快速而干净地失败,而不是冒着在未来不确定的时间出现任意、非确定性行为的风险。
- 请注意,无法保证迭代器的快速故障行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。快速失败迭代器会
尽最大努力抛出 ConcurrentModificationException
。因此,编写一个依赖于此异常来判断正确性的程序是错误的;迭代器的快速故障行为应仅用于检测错误
三、前置知识
在ArrayList中在进行扩容、删除值、增加值的操作都会使用到数组复制操作,在源码中会涉及到两种:
- System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)-System类提供的native方法
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);// 表示从src的srcPos位置拷贝length个值到dest从destPos开始的位置
- Arrays.copyOf(elementData, newCapacity)- Arrays类提供的方法
public static <T> T[] copyOf(T[] original, int newLength) {// 传递一个original数组和一个newLength最后返回一个新数组
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength] // 直接new一个newLength长度的数组
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));// 使用arraycopy执行copy操作
return copy;// 返回新数组
}
四、继承体系
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
从上面的继承关系中可以看出,ArrayList至少有以下几大特性:
- 实现Cloneable接口,说明是支持克隆的
- 继承AbstractList得到AbstractList封装的通用逻辑
- 实现RandomAccess接口,说明支持快速随机访问,下面会介绍
- 实现Serializable接口,说明支持序列化和反序列化
五、RandomAccess接口
public interface RandomAccess {
}
淦。发现真的只是一个接口然后什么都没了,看看官方解释:
List实现使用RandomAccess标记接口表明它们支持快速(通常是恒定时间)随机访问。此接口的主要目的是允许通用算法在应用于随机或顺序访问列表时改变其行为以提供良好的性能
。
用于操作随机访问列表(例如ArrayList )的最佳算法在应用于顺序访问列表(例如LinkedList )时会产生二次行为。鼓励通用列表算法在应用算法之前检查给定列表是否是此接口的实例,如果将其应用于顺序访问列表会提供较差的性能,并在必要时更改它们的行为以保证可接受的性能。
众所周知,随机访问和顺序访问之间的区别通常是模糊的。例如,如果某些List实现变得很大,则提供渐近线性访问时间,但在实践中访问时间是恒定的。这样的List实现一般应该实现这个接口。根据经验,如果对于类的典型实例,如果出现以下循环,则List实现应该实现此接口:比如当前要讲的ArrayList使用下面的迭代方式才快。
for (int i=0, n=list.size(); i < n; i++)
list.get(i);
运行速度比这个循环快:【注意:LinkedList使用这种遍历方式更快,因为LinkedList没有继承RandomAccess接口
】
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();
可以看看ArrayList和LinkedList不同遍历方式的耗时差距:
ArrayList traverse : 13 【普通遍历方式】
ArrayList traverse by iterator : 31 【迭代器方式】
ArrayList traverse by foreach : 34 【foreach方式】LinkedList traverse : 6422 【普通遍历方式】
LinkedList traverse by iterator: 19【迭代器方式】
LinkedList traverse by foreach : 7【foreach方式】
六、核心变量
// 默认容量大小为10
private static final int DEFAULT_CAPACITY = 10;
// 【使用有参构造的方式,但是initialCapacity为0的时候】对应的空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认容量下【使用无参构造的方式】指向的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存放数据的数组
transient Object[] elementData; // non-private to simplify nested class access
// 当前list中存放值的个数
private int size;
// 支持的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
七、构造方法
无参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;// 指向默认的空数组,注意:DEFAULTCAPACITY_EMPTY_ELEMENTDATA和上面的EMPTY_ELEMENTDATA在扩容的时候就能看出这两个之间的区别
}
有参构造
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];// 直接new一个长度为initialCapacity的数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;// 标记空数组,elementData指向EMPTY_ELEMENTDATA
} else {// 如果initialCapacity小于0直接抛异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
// 参数为一个Collection
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();// elementData直接指向一个数组
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
// 源码中已经说了,c.toArray返回的值有可能不是Object[]类型
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);// 将elementData转换为Object[]类型
} else {// 说明elementData的size为0
this.elementData = EMPTY_ELEMENTDATA;// 直接替换为EMPTY_ELEMENTDATA
}
}
八、核心方法
添加值
add(E e)往末尾添加值
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 1.首先对容量进行检查,看看当前添加一个值到容器中,容量够不够,不够的话需要进行扩容
elementData[size++] = e;// 2.添加值,能执行到这里,说明容量一定是够的;而且可以看出,add(E e)默认是在列表尾部进行添加操作
return true;
}
反正就两步逻辑:
- 确保数组容量是够的【利用扩容机制】
- 将值添加到数组的末尾
add(int index, E element)指定位置插入值
public void add(int index, E element) {
rangeCheckForAdd(index);// 1.检查index是否合法
ensureCapacityInternal(size + 1); // 2.执行容量检测或扩容操作,并让modCount++
System.arraycopy(elementData, index, elementData, index + 1,
size - index);// 3.执行数组copy操作,把index位置给腾出来
elementData[index] = element;// 4.替换值操作
size++;// 5.增加size
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
从源码中可以看出,在指定位置添加值会涉及到数组的copy操作,就意味着比较耗性能
addAll(Collection<? extends E> c)将一个容器的值全部添加到ArrayList的末尾
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();// 转换为数组
int numNew = a.length;
ensureCapacityInternal(size + numNew); // 确保数组容量够用
System.arraycopy(a, 0, elementData, size, numNew);// 执行数组copy操作
size += numNew;// 更新size
return numNew != 0;
}
addAll(int index, Collection<? extends E> c)将一个容器的值全部添加到ArrayList的index位置往后
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);// 1.检查index是否合法
Object[] a = c.toArray();// 2.得到数组
int numNew = a.length;
ensureCapacityInternal(size + numNew); // 3.确保数组的长度够用
int numMoved = size - index;// 4.计算移动的个数
if (numMoved > 0)// 5.将index往后腾出numNew个位置
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);// 6.将a数组的值copy到elementData的index位置往后
size += numNew;
return numNew != 0;
}
扩容机制
// 扩容操作
private void ensureCapacityInternal(int minCapacity) {// minCapacity就是需要的容量大小
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 1.计算需要的容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {// 能执行到这个分支,说明是使用的无参构造的方式创建ArrayList,并且是第一次调用add或addAll方法
return Math.max(DEFAULT_CAPACITY, minCapacity);//取最大值,DEFAULT_CAPACITY默认为10,然后得到minCapacity和10之间的最大值,因为minCapacity在addAll的时候可能会大于10
}
return minCapacity;// 返回需要的容量
}
// 2.扩容操作的判断
private void ensureExplicitCapacity(int minCapacity) {
modCount++;// 防止迭代器修改,fail-fast机制会用到,就相当于纪录修改的版本号
if (minCapacity - elementData.length > 0)// 说明数组容量确实不够用了,需要进行扩容
grow(minCapacity);// 真正执行扩容操作
}
// 3.真正执行扩容操作
private void grow(int minCapacity) {
int oldCapacity = elementData.length;// 得到旧的容量
int newCapacity = oldCapacity + (oldCapacity >> 1);// 等价于1.5 * oldCapacity,也就是扩容为原来的1.5倍
if (newCapacity - minCapacity < 0)// 新容量还是小于需要的容量;上面不是已经扩容了,为啥还小于需要的容量呢???假设一种情况就是在创建ArrayList的时候使用有参构造的方式,将容量设置为0,如果第一次执行add操作的时候,会执行到这里,就会发现上面使用1.5倍计算出来的newCapacity还是为0,显然newCapacity不能为0,所以这里就让newCapacity变为1执行后续的扩容操作
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)// newCapacity已经超过最大的容量了
newCapacity = hugeCapacity(minCapacity);
// 创建新数组并将elementData中的值copy到新数组中,elementData指向新的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 防止容量超过最大值
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();// 直接超过最大值,抛出OOM
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :// 从这里可以看出,ArrayList支持最大的容量为Integer.MAX_VALUE
MAX_ARRAY_SIZE;
}
扩容机制有核心的三个步骤:
- 计算当前需要的容量大小
- 判断是否真正需要进行扩容,并执行modCount++
- 执行真正的扩容操作,扩容为原来数据长度的1.5倍,并且会判断长度是否超过容量的最大值,最后使用elementData = Arrays.copyOf(elementData, newCapacity);创建新数组并copy旧数组所有的值到新数组中
删除值
remove(int index)-删除指定位置的值
public E remove(int index) {
rangeCheck(index);// 1.合法性检查
modCount++;// 2.fail-fast机制
E oldValue = elementData(index);// 3.根据index位置拿到oldValue,时间复杂度为O(1),因为直接从数组下标取值
int numMoved = size - index - 1;// 4.计算需要移动数据个数
if (numMoved > 0)// 说明被删除数据不是末尾的数据
System.arraycopy(elementData, index+1, elementData, index,
numMoved);// 通过copy的方法去删除值
elementData[--size] = null; // 让末尾位置置空,for gc
return oldValue;
}
E elementData(int index) {
return (E) elementData[index];// 直接就得到数组下标位置的值
}
从源码中可以知道,该方法的时间复杂度为O(N),主要是涉及到数组拷贝
remove(Object o)-删除匹配的值
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;
}
// 删除index位置的值
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;// 计算移动元素的个数
if (numMoved > 0)// 不是数组最后一个位置
System.arraycopy(elementData, index+1, elementData, index,
numMoved);// 数组copy操作,将index + 1往后所有的数据copy到index往后
elementData[--size] = null; // clear to let GC do its work
}
从源码中可以知道,该方法的时间复杂度为O(N)
removeAll(Collection<?> c)-从此列表中删除指定集合中包含的所有元素
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
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++)
if (c.contains(elementData[r]) == complement)// 注意,这里的complement是false
elementData[w++] = elementData[r];// 说明在c中不包含elementData中的元素
// 如果c中包含elementData中的元素,w不进行++操作,说明需要被删除
} finally {
if (r != size) {// 说明出现异常了
System.arraycopy(elementData, r,
elementData, w,
size - r);// 把elementData后面还没有遍历的值直接copy到r开始位置
w += size - r;
}
if (w != size) {// 说明w后面的都应该需要被删除的,w前面的都是不需要被删除的
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;// 用于GC操作
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
从源码可以看出,使用双指针的方式实现删除,一个w写指针,一个r读指针,w指针用于保存不需要被删除的值,最后w之前的都是需要保留的,w之后的都是需要删除 ,而且还能保持元素的相对顺序。
retainnAll(Colllection<?> c)-仅保留此列表中包含在指定集合中的元素
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
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++)
if (c.contains(elementData[r]) == complement)// 注意,这里的complement是true
elementData[w++] = elementData[r];// 说明在c中包含elementData中的元素,需要删除被保存
// 如果c中不包含elementData中的元素,直接删除
} finally {
if (r != size) {// 说明出现异常了
System.arraycopy(elementData, r,
elementData, w,
size - r);// 把elementData后面还没有遍历的值直接copy到r开始位置
w += size - r;
}
if (w != size) {// 说明w后面的都应该需要被删除的,w前面的都是不需要被删除的
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;// 用于GC操作
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
修改值
public E set(int index, E element) {
rangeCheck(index);// 1.检查index位置是否合法
E oldValue = elementData(index);// 2.得到index位置的值
elementData[index] = element;// 3.直接替换为element
return oldValue;// 4.返回旧值
}
从源码中可以知道,该方法的时间复杂度为O(1)
获取值
get(int index)得到指定位置的值
public E get(int index) {
rangeCheck(index);// 校验index的合法性
return elementData(index);// 查询index位置的值
}
E elementData(int index) {
return (E) elementData[index];// 再简单不过了
}
从源码中可以知道,该方法的时间复杂度为O(1)
是否包含某值
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {// 时间复杂度为O(N)
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 Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // 游标控制
int lastRet = -1; // 返回-1表示没有数据了
int expectedModCount = modCount;// 记录数据版本,防止使用迭代器的时候,其他线程删除增加数据
Itr() {}
public boolean hasNext() {
return cursor != size;// 判断cursor是否到末尾,到底末尾说明没数据了
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();// 检查modCount标记
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
// 得到数组
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)// 游标位置已经比数组长度还长,说明有其他线程并发remove了数据,直接抛出异常
throw new ConcurrentModificationException();
cursor = i + 1;// 游标往下走一个
return (E) elementData[lastRet = i];// 返回值
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();// 检查modCount
try {
ArrayList.this.remove(lastRet);// 进行删除操作
cursor = lastRet;// 游标需要回到之前的位置(因为马上删除一个值,下一个数的位置就是当前被删值的位置)
lastRet = -1;
expectedModCount = modCount;// 因为执行了删除了操作,所以modCount已经递增了,需要重新设置一下
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
十、其他
clear()-清除整个容器
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;// 每个位置变为null,使GC回收
size = 0;// 重置为0
}
sort(Comparator<? super E> c)-排序操作
@Override
@SuppressWarnings("unchecked")
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++;
}
trimToSize()-把数组长度缩小为当前元素个数
将此 ArrayList 实例的容量修剪为列表的当前大小,应用程序可以使用此操作来最小化 ArrayList 实例的存储
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);// 直接得到一个新数组,长度为siz
}
}
序列化相关
序列化
// 源码
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
int expectedModCount = modCount;
s.defaultWriteObject();
s.writeInt(size);// size长度
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);// 只写入size元素的数据,这样就能节省空间,不需要把整个数组进行序列化,也没必要这么做
}
// ArrayList被并发修改,抛出ConcurrentModificationException
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
// 使用
public static void testWrite() {
ArrayList<User> users = new ArrayList<>();
users.add(new User("zs", 12));
users.add(new User("luopo", 18));
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream("/Users/apple/Desktop/io_test/user.ser");
oos = new ObjectOutputStream(fos);
oos.writeObject(users);// 这个方法最后就会调用上面的writeObject方法
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
oos.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
反序列化
// 源码
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
s.defaultReadObject();
s.readInt(); // ignored
if (size > 0) {
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);// 确保ArrayList的空间足够
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();// 执行readObject操作
}
}
}
// 使用
public static void testRead() {
List<User> users = new ArrayList<>();
users.add(new User("zs", 12));
users.add(new User("luopo", 18));
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream("/Users/apple/Desktop/io_test/user.ser");
ois = new ObjectInputStream(fis);
ArrayList<User> readUsers = (ArrayList<User>) ois.readObject();// 最后会执行到上面源码readObject方法
System.out.println(readUsers);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
ois.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ArrayList提供的序列化方式可以节省存储空间和IO传输时间,因为ArrayList底层是一个数组,如果只有add一个元素,数组是10,剩下9个位置都是null,所以,剩下9个位置的数据就没必要进行序列化
十一、常见面试题目
ArrayList默认容量大小是多少???
默认为:10
ArrayList多久初始化数组???
-
有参构造
-
参数小于0,直接抛出异常
- 参数为0,数组直接指向一个空对象{}
- 参数不为0,数组直接初始化new一个长度为传入参数的数组
-
无参构造
- 在第一次调用add方法的时候,会进行容量判断,然后使用10为默认数组长度进行数组的初始化
ArrayList扩容机制???
在每次添加add的时候,都会去校验当前容器中容量够不够,如果不够就需要进行扩容操作:
- 计算新容量大小,默认为1.5 * oldCap(需要校验容量的合法性,比如是不是超过了Integer.MAX_VALUE:2 ^ 31 - 1)
- elementData = Arrays.copyOf(elementData, newCapacity),执行数组的扩容和copy操作
Arrays.copyOf()和System.arraycopy()的对比???
-
System.arraycopy()非常灵活, 可以指定从src的哪个索引处开始拷贝, 可以指定拷贝的长度, 还可以指定从dst的哪个索引处开始接收src的元素; 相比之下, Arrays.copyOf()是从src[0]开始拷贝的, 也是从dst[0]开始接收的, 而且也没法指定拷贝的长度
-
System.arraycopy()是native方法, 不是由java语言实现的; Arrays.copyOf()是由java语言实现的, 不过底层是基于System.arraycopy()方法
ArrayList如何删除一个值???
- 校验index位置是否合法
- 通过System.copy执行删除操作(实际上还是移动数组上各个元素来完成删除操作)
ArrayList是否是线程安全的???
肯定不是啊,可以使用synchronized进行上锁
ArrayList建议使用什么遍历方式???LinkedList呢???
-
由于ArrayList继承了RandomAccess接口,说明使用直接索引遍历会比迭代器方式快;
-
LinkedList则反之,使用迭代器方式更快(因为LinkedList使用的index,会从头遍历查找index位置,从而消耗性能)
十二 、总结
看了ArrayList的源码,这里可以进行几个简单的总结:
功能角度
ArrayList是一个Java的Collection,可以存放null和其他引用类型的值,并且是顺序存放的
底层角度
ArrayList底层是利用数组进行实现,数组默认长度为10,每次扩容为原来长度的1.5倍;由于底层使用的是数组,所以对于值的删除、中间插入需要进行数组的copy操作,需要消耗一定性能。但是,对于获取值get(i)的操作非常快,因为直接从数组下标取值。
线程安全角度
ArrayList是线程不安全的,想要使用一个线程安全的List,有如下两种方式:
- 使用线程安全的Vector
- 使用Collections.synchronizedList(new ArrayList(…));进行包装
- 使用CopyOnWriteArrayList
在使用迭代器进行迭代遍历的时候,如果此时出现多线程并发执行的add、remove等改变ArrayList结构的方法会导致抛出 ConcurrentModificationException
异常,也就是所谓的fail-fast机制