【本文是为了梳理知识的总结性文章,总结了一些自认为相关的重要知识点,只为巩固记忆以及技术交流,忘批评指正。其中参考了很多前辈的文章,包括图片也是引用,如有冒犯,侵删。】
目录
0 存储结构
从底层实现来看,Array是数组实现的,与数组不同的是,其容量是可以改变的。集合扩容的时候会创建更大的数组空间,把原有数据复制到新数组中。ArrayList支持对元素的快速随机访问,但是插入和删除 时速度通常很慢,因为这个过程很有可能需要移动其他元素。
1 类定义
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
2 静态常量
// 默认初始容量大小
private static final int DEFAULT_CAPACITY = 10;
// 空数组(用于空实例)
private static final Object[] EMPTY_ELEMENTDATA = {};
// 用于默认大小空实例的共享空数组实例。
// 我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 数组能够分配的理论上的最大值
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
3 属性
/**
* 保存ArrayList数据的数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* ArrayList 所包含的元素个数
*/
private int size;
4 构造函数
主要用于初始化数组。
/**
* 带初始容量参数的构造函数。
*/
public ArrayList(int initialCapacity) {
// initialCapacity > 0,则直接创建相应长度的数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 使用EMPTY_ELEMENTDATA表示创建了空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 默认构造函数,使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA表示为空数组.
* 当添加第一个元素后才会真正分配一个长度为10的数组
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 按照它们由集合的迭代器返回的顺序,构造一个包含指定集合的元素的列表。
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
// 如果指定集合元素个数不为0
if ((size = elementData.length) != 0) {
// 判断c.toArray 是否返回Object类型的数组,不是的话用Arrays.copyOf复制
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 用空数组代替
this.elementData = EMPTY_ELEMENTDATA;
}
}
5 常用方法
add方法
- 确保数组能够放下该元素,容量不够则扩容;
- 将指定的元素追加到此列表的末尾
public boolean add(E e) {
// 判断是否需要扩容,需要则使用grow方法扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将指定的元素追加到此列表的末尾
elementData[size++] = e;
return true;
}
判断是否需要扩容,需要则调用grow()方法扩容
// 得到最小扩容量
private void ensureCapacityInternal(int minCapacity) {
// elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA表示目前数组长度是默认长度10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 看新增一个节点后长度是否比默认长度大
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
// 新增属于结构性修改
modCount++;
// 如果比当前数组长度大,则需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
扩容方法
- 确定新容量的大小,首先扩大到1.5倍,如果不够用则直接使用所需的容量;
- 如果所需容量超过了理论上分配的最大值,则使用最大容量;
- 复制数组到新容量的新数组中。
// ArrayList扩容方法
private void grow(int minCapacity) {
// oldCapacity为旧容量,newCapacity为新容量
int oldCapacity = elementData.length;
// 新容量为救容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 新容量还是不满足要求,则直接使用minCapacity作为新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 新容量如果大于ArrayList所定义的最大容量,则将其设置为MAX_ARRAY_SIZE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 使用复制Arrays.copyOf 复制到新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
数组复制方法
源码中有时候会用Arrays.copyOf()方法,有时候会用System.arraycopy()方法进行数组复制,其实Arrays.copyOf()方法底层是调用了System.arraycopy()方法,而System.arraycopy()方法是一个Native方法。
Arrays.copyOf(T[] original, int newLength)方法
复制指定的original 数组,以使长度为newLength。
原数组中有效的值将包含在新数组中,新数组多出来的部分为null。
底层实现调用了 System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
System.arraycopy(Object src, int srcPos, Object dest, int destPos,int length)方法
将原数组src从原数组起始位置srcPos复制到目的数组dest的destPos位置,复制长度为length。
参数 | 说明 |
src | 原数组 |
srcPos | 原数组起始位置 |
dest | 目标数组 |
destPos | 目标数组的起始位置 |
length | 要复制的数组元素的数目 |
// Arrays.copy方法源码
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
// 底层使用了使用System.arraycopy()方法,将旧数组数据复制到新数组
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
// 声明一个长度为newLength的新数组
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
//
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
// 是一个native方法
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
get方法
- 简单检查index是否大于数组长度;
- 直接返回该元素。
/**
* 返回 ArrayList 中指定位置的元素。
*/
public E get(int index) {
// 对index进行数组边界检查
rangeCheck(index);
// 直接返回该位置的元素
return elementData(index);
}
// 只检查了是否大于数组长度
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
remove方法
- 索引边界检查
- 自增修改次数
- 将index上的元素保存到oldValue
- 将index上的元素都往前移动一位
- 将最后面的一个元素置空,好让垃圾回收器回收
- 将原来的值oldValue返回
// 删除该ArrayList指定位置的元素
public E remove(int index) {
// 边界检查
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
// 计算需要移动的元素个数
int numMoved = size - index - 1;
// 将index后面的元素向前移动一位
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将最后一位置为null,帮助GC
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
remove指定对象方法
- 根据对象是否为空有两种处理逻辑;
- 移除对象为空则依次查找第一个为null的对象,然后进行移除;
- 移除对象不为空则依次查找,找到相等的元素进行移除。
// 移除指定对象
public boolean remove(Object o) {
// 根据对象是否为空有两种处理逻辑
// 移除对象为空则依次查找第一个为null的对象
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;
}
// 跳过检查直接移除指定位置的元素,逻辑和remove(int index)一样
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
}
clear()方法
public void clear() {
modCount++;
// 把数组元素都置为null
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
addAll()方法
public boolean addAll(Collection<? extends E> c) {
// 转化为Object数组
Object[] a = c.toArray();
int numNew = a.length;
// 保证可插入
ensureCapacityInternal(size + numNew); // Increments modCount
// 数组复制
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
从指定位置进行添加
与直接在数组末尾添加相比,指定位置插入需要移动原数组中的index后面的元素。
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
// 需要计算移动数组的长度
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
6 迭代器
迭代器模式(Iterator Pattern):提供一种方法来访问聚合对象中的各个元素,而不用暴露这个对象的内部表示。在Java中,ArrayList的迭代器有两种:Iterator和ListIterator。
Iterator
public interface Iterator<E> {
// 集合是否被遍历完成
boolean hasNext();
// 返回下一个元素
E next();
// 移除上一次next()返回的元素
default void remove() {
throw new UnsupportedOperationException("remove");
}
// 对集合中的剩余元素采取特定操作
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
ArrayList 中的内部类Itr实现了迭代器接口。
在ArrayList中使用Iterator遍历时,不能使用list.add()、list.remove()等方法对底层数组进行结构性修改,如果要修改只能使用it.remove()进行移除。
这是因为ArrayList不是线程安全的,如果在Iterator 时还有别的线程做增删操作,必然会有问题,如数组下标越界等。
因此Iterator迭代器实现增加了对modCount的校验,如果失败就会快速失败。快速失败”即fail-fast,它是java集合的一种错误检测机制。当多钱程对集合进行结构上的改变或者集合在迭代元素时直接调用自身方法改变集合结构而没有通知迭代器时,有可能会触发fast-fail机制并抛出异常。
而在迭代器可以remove,因为它自己删除就不是并发修改,迭代器remove会重置expectedModCount,并将cursor往前一位。
// 返回Itr迭代器对象
public Iterator<E> iterator() {
return new Itr();
}
/**
* 内部类实现了迭代器接口
*/
private class Itr implements Iterator<E> {
int cursor; // 下一个返回元素的索引
int lastRet = -1; // 上一个返回元素的索引
int expectedModCount = modCount;
// 判断迭代器是否遍历完成
public boolean hasNext() {
return cursor != size;
}
// 返回下一个元素
@SuppressWarnings("unchecked")
public E next() {
// 检查是否有外部的结构性修改,有就fast-fail
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];
}
// 内部删除
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
// 检查迭代期间是否有外部方法对数组进行了结构性修改,有就抛出异常
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ListIterator迭代器
ListIterator 继承了Iterator接口,相比于Iterator接口添加了previous等方法,使之具有了双向的查找能力,还可以进行添加、设置等修改方法。
public interface ListIterator<E> extends Iterator<E> {
// 查询操作
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
// 修改操作
void remove();
void set(E e);
void add(E e);
ArrayList中的ListItr继承了Itr内部类,并实现了ListIterator接口,是一个功能更加强大的Iterator的子类型。它只能用于各种List类的访问。它最大的优点是可以双向移动。它还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素。
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
// cursor 不等于0表示还有前一个元素
public boolean hasPrevious() {
return cursor != 0;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
7 subList
subString 方法来对 String 对象进行分割处理,但是使用这个方法有很多需要注意的地方。
subListRangeCheck 方式是判断 fromIndex、toIndex 是否合法,如果合法就直接返回一个 subList 对象。
注意在产生该 new 该对象的时候传递了一个参数 this ,该参数非常重要,因为他代表着原始 list。
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
// 边界检查
static void subListRangeCheck(int fromIndex, int toIndex, int size) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > size)
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
}
subList 返回仅仅只是一个视图
该 SubLsit 是 ArrayList 的内部类,它与 ArrayList 一样,都是继承 AbstractList 和实现 RandomAccess 接口。同时也提供了 get、set、add、remove 等 list 常用的方法。但是它的构造函数有点特殊,在该构造函数中有两个地方需要注意:
- this.parent = parent;而 parent 就是在前面传递过来的 list,也就是说 this.parent 就是原始 list 的引用。
- this.offset = offset + fromIndex; this.parentOffset = fromIndex;。同时在构造函数中它甚至将 modCount(fail-fast机制)传递过来了。
我们再看 get 方法,在 get 方法中 return ArrayList.this.elementData(offset + index);
这段代码可以清晰表明 get 所返回就是原列表 offset + index位置的元素。
到了这里我们可以判断 subList 返回的 SubList 同样也是 AbstractList 的子类,同时它的方法如 get、set、add、remove 等都是在原列表上面做操作,它并没有像 subString 一样生成一个新的对象。所以 subList 返回的只是原列表的一个视图,它所有的操作最终都会作用在原列表上。
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
public int size() {
checkForComodification();
return this.size;
}
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
protected void removeRange(int fromIndex, int toIndex) {
checkForComodification();
parent.removeRange(parentOffset + fromIndex,
parentOffset + toIndex);
this.modCount = parent.modCount;
this.size -= toIndex - fromIndex;
}
public boolean addAll(Collection<? extends E> c) {
return addAll(this.size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
int cSize = c.size();
if (cSize==0)
return false;
checkForComodification();
parent.addAll(parentOffset + index, c);
this.modCount = parent.modCount;
this.size += cSize;
return true;
}
public Iterator<E> iterator() {
return listIterator();
}
// .......
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, offset, fromIndex, toIndex);
}
private void rangeCheck(int index) {
if (index < 0 || index >= this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void rangeCheckForAdd(int index) {
if (index < 0 || index > this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+this.size;
}
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
public Spliterator<E> spliterator() {
checkForComodification();
return new ArrayListSpliterator<E>(ArrayList.this, offset,
offset + this.size, this.modCount);
}
}
生成子列表后,不要试图去操作原列表,否则会造成子列表的不稳定而产生异常
最佳实践
- 预估使用场景所需的容量,并设置为初始值,防止多次扩容,影响性能(尤其在数据量大的时候);
-
推荐使用 subList 处理局部列表
在开发过程中我们一定会遇到这样一个问题:获取一堆数据后,需要删除某段数据。例如,有一个列表存在 1000 条记录,我们需要删除 100-200 位置处的数据,可能我们会这样处理:
for(int i = 0 ; i < list1.size() ; i++){ if(i >= 100 && i <= 200){ list1.remove(i); /* * 当然这段代码存在问题,list remove之后后面的元素会填充上来, * 所以需要对i进行简单的处理,当然这个不是这里讨论的 问题。 */ } }
这个应该是我们大部分人的处理方式吧,其实还有更好的方法,利用 subList。在前面 LZ 已经讲过,子列表的操作都会反映在原列表上。所以下面一行代码全部搞定:
list1.subList(100, 200).clear();
参考文献
- https://zhuanlan.zhihu.com/p/34443888
- https://blog.youkuaiyun.com/wangnan9279/article/details/79287399
- http://wiki.jikexueyuan.com/project/java-enhancement/java-thirtyseven.html
- 《码出高效 Java开发手册》
- 《编写高质量代码:改善 Java 程序的 151 个建议》