ArrayList是在Java开发中使用率很高、很常见的集合类,它继承自AbstractList,实现List接口,并实现了 RandomAccess, Cloneable, java.io.Serializable标记接口,底层基于可扩容的数组实现,允许Null存在,同时支持快速访问、序列化、复制。
继承结构
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- ArrayList继承AbstractList,并实现了List接口,但AbstractList同样实现了List接口,为什么这样设计?
AbstractList为抽象类,而抽象类可以有抽象方法,也可以有实现方法,利用这一点,在AbstractList抽象类中实现一些通用的方法,子类可以继承到通用方法也可以重写各自特有的方法,代码更加简洁。
至于 ArrayList和AbstractList为什么都实现了List接口,在网上看到了一个有意思的回答Why does LinkedHashSet extend HashSet and implement Set:说是一个误会,开发时以为会有价值,所以就留着了,但实际并没有什么用途,不过也没有影响,就一直没有去维护…
- RandomAccess, Cloneable, java.io.Serializable这几个接口都是空接口,作用是什么?
这类没有属性和方法的接口是标记接口,Java中的一个标记接口表示的的是一种类的特性,实现了该标记接口的类则具有该特性。如实现了Serializable接口的类,表示这个类的对象是可以进行序列化和反序列化的。Java中常见的标记接口还有Cloneable接口、RandomAccess接口和Remote接口。可以用 if(对象名 instanceof 标记接口名)检测一个类是否实现某个标记接口。
- RandomAccess接口:用来快速随机存取,有关效率的问题,在实现了该接口的话,那么使用普通的for循环来遍历,性能更高;若未实现,则使用Iterator来迭代,这样性能更高;
- Cloneable接口:该接口标记一个类的对象是否有安全的clone方法;
- Serializable接口:实现该序列化接口,表明该类可以被序列化;
静态变量
// 序列化版本号
private static final long serialVersionUID = 8683452581122892189L;
// 缺省(默认)容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组对象
private static final Object[] EMPTY_ELEMENTDATA = {};
// 缺省(默认)空数组对象
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 元素数组
transient Object[] elementData;
// 实际元素数量,默认0
private int size;
// 最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
- EMPTY_ELEMENTDATA 和DEFAULTCAPACITY_EMPTY_ELEMENTDATA 两个空数组对象分别有什么用?
源码中在DEFAULTCAPACITY_EMPTY_ELEMENTDATA 定义处存在以下的注释:“We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.”,大意是我们将两个数组区分开,由此知道在添加第一个元素时应该扩容多少。
- 为什么最大数组容量是Integer.MAX_VALUE - 8 而不是 Integer.MAX_VALUE ?
- transient 修饰词的作用?
构造方法
ArrayList有三种构造方法,分别为无参、指定大小、指定集合三种形式。
- 无参构造函数:elementData 指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA,是一个空数组,在第一次添加数据时才会将数组长度设置为10。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- 指定长度的构造函数
若initialCapacity >0 ,则初始化为指定长度数组;
若initialCapacity == 0,则初始化为EMPTY_ELEMENTDATA空数组;
若initialCapacity < 0,则抛出异常。
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);
}
}
- 指定Collection 的构造方法
若传入的Collection为null,则抛出NullPointerException 异常;
若c.toArray().length == 0,则指向EMPTY_ELEMENTDATA;
若c.toArray().length != 0,则将elementData 指向Collection 转化后(可能需要转化两次)的数组。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
// toArray方法的实现不同,有些不会返回Object[]类型的数组,所以要增加一个判断
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
主要方法
添加元素-add/addAll
ArrayList集合提供了四种添加元素的方法:
// 在最后加一个元素
public boolean add(E e){...}
// 在指定位置添加一个元素,需满足:0 <= index <= size
public void add(int index, E element){...}
// 利用System.arraycopy(...)函数将c中元素加到list末尾
public boolean addAll(Collection<? extends E> c){...}
// 指定位置插入集合元素
public boolean addAll(int index, Collection<? extends E> c){...}
四种方法的逻辑大致相同,此处以第二个方法为例,主要看一下调用到的方法。
public void add(int index, E element) {
// 0 <= index <= size,否则IndexOutOfBoundsException
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
// 用于判断index是否符合要求:0 <= index <= size
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 数组越界消息拼接
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
// 计算最小容量
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
// 为了判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
// 操作数++,modCount来自AbstractList,后续再研究
modCount++;
// overflow-conscious code
// 如果最小容量>当前数组长度,则进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 真正的扩容方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// oldCapacity + (oldCapacity >> 1) ==> oldCapacity*1.5
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 原理:建一个新数组,copy旧数组
// 所以在初始化数组时,在数据长度已知或有范围时,尽量指定初始化大小,减少扩容次数,避免时间、空间浪费
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 超大数组...走到这一步的代码..不,支撑这段代码走到这一步的服务器真牛*
private static int hugeCapacity(int minCapacity) {
// 讲道理...这个地方不会小于0吧
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 这种情况基本不会出现,这么大的数组一般都OOM了,如果没有OOM,一定是需求不合理或者代码不合理
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
指定位置插入时还需要用到System.arraycopy()方法,就是将elementData在插入位置后的所有元素往后面移。
/**
* @param src 数据源
* @param srcPos 数据源起始位置
* @param dest 目标对象
* @param destPos 目标对象起始位置
* @param length 操作长度
* Demo:System.arraycopy(elementData, index, elementData, index + 1, size - index);
**/
public static native void arraycopy(Object src,int srcPos,Object dest, int destPos,int length);
移除元素-remove/removeAll/retainAll/clear/removeIf
ArrayList集合提供了五种移除元素的方法:
- remove(int index):移除指定位置的元素,并返回remove的元素
public E remove(int index) {
// 校验index范围
rangeCheck(index);
modCount++;
// remove的元素
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;
}
// 校验index范围(纳闷:这为啥不判断index<0?直接抛IndexOutOfBoundsException)
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
- remove(Object o):移除指定元素(以equals方法匹配,至多remove一个),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;
}
// 跳过rangeCheck&&不返回删除的元素(remove(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():移除全部元素(所有位置指向null)
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
- removeAll(Collection<?> c):移除指定集合的元素
public boolean removeAll(Collection<?> c) {
// 若c为空,则抛出NullPointerException
Objects.requireNonNull(c);
return batchRemove(c, false);
}
// 批量移除(complement==false表示去掉交集,complement==true表示只保留交集)
private boolean batchRemove(Collection<?> c, boolean complement) {
// 疑问,这样做的意义是什么?
final Object[] elementData = this.elementData;
int r = 0, w = 0;
// 标记是否修改集合
boolean modified = false;
try {
// 小机灵鬼,w<=r恒成立,所以把不包含的元素往前排
// 最终[0,w)就是留下的元素,循环删除[w,siz)的元素即可
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
// 如果c.contains()抛出异常,则保留[r,size)的元素
if (r != size) {
System.arraycopy(elementData, r, elementData, w, size - r);
w += size - r;
}
// w!=size,存在不符合条件的,指向null
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
// 判断当前集合是否包含某个对象(此处以ArrayList的实现为例)
// true:包含,false:不包含
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
// 查找元素,返回第一个匹配的元素所在的索引值
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;
}
- retainAll(Collection<?> c):与removeAll(Collection<?> c)相反,retainAll用来取交集,不再展开分析
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
- removeIf(Predicate<? super E> filter):删除符合条件的元素
@Override
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
// figure out which elements are to be removed
// any exception thrown from the filter predicate at this stage
// will leave the collection unmodified
int removeCount = 0;
final BitSet removeSet = new BitSet(size);
final int expectedModCount = modCount;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
final E element = (E) elementData[i];
if (filter.test(element)) {
// 保留需要删除的元素的索引
removeSet.set(i);
removeCount++;
}
}
// 防止并发修改
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
// shift surviving elements left over the spaces left by removed elements
final boolean anyToRemove = removeCount > 0;
if (anyToRemove) {
final int newSize = size - removeCount;
for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
// 实话说..没看懂,据网上介绍:方法返回第一个位出现或之后指定的起始索引被设置为false的索引
// 写了个测试类,感觉像是找下一个值
// eg:bitSet.set(1.2.3...10);
// System.out.println(bitSet.nextClearBit(3));->10
// System.out.println(bitSet.nextClearBit(10));->10
// System.out.println(bitSet.nextClearBit(11));->11
// System.out.println(bitSet.nextClearBit(13));->13
i = removeSet.nextClearBit(i);
// 此处将不符合条件的按顺序放到数组中
elementData[j] = elementData[i];
}
// 末尾替换成null
for (int k=newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work
}
this.size = newSize;
// 防止并发修改
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
return anyToRemove;
}
截取集合-subList
- subList(int fromIndex, int toIndex):截取List
“非结构性修改”:是指不涉及到list的大小改变的修改;
“结构性修改”,指改变了list大小的修改;
/**
* 通过方法注释可以知道:
* 1. 该方法返回的是父list的一个视图,[fromIndex,toIndex);
* 2. 父子list做的非结构性修改(non-structural changes)都会影响到彼此;
* 3. 对于结构性修改,子list的所有操作都会反映到父list上,但父list的修改将会导致返回的子list失效;
**/
public List<E> subList(int fromIndex, int toIndex) {
// 类方法,检查参数合规性,会抛出IndexOutOfBoundsException/IllegalArgumentException异常
subListRangeCheck(fromIndex, toIndex, size);
// 此处返回的是SubList
return new SubList(this, 0, fromIndex, toIndex);
}
// SubList是arrayList的一个匿名内部类,所有的操作方法均是操作的原来的ArrayList,即“parent”变量
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;
}
...
}
通过源码发现subList返回的是一个SubList内部类,且父集合做结构性修改时,会导致子集合失效(如果必须用subList,最好随用随取,防止父集合结构性修改导致的异常):
public static void main(String[] args) throws Exception {
List<String> arrayList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
arrayList.add("" + i);
}
List<String> subList = arrayList.subList(5, 7);
System.out.println(JsonUtils.toJson(arrayList));
System.out.println(JsonUtils.toJson(subList));
System.out.println("父子集合做非结构性修改,正常输出");
arrayList.set(5,11 + "");
subList.set(1,12+"");
System.out.println(JsonUtils.toJson(arrayList));
System.out.println(JsonUtils.toJson(subList));
System.out.println("子集合做结构性修改,影响父集合,正常输出(注意13插入的位置)");
subList.add(13 + "");
System.out.println(JsonUtils.toJson(arrayList));
System.out.println(JsonUtils.toJson(subList));
System.out.println("父集合做结构性修改,子集合失效,访问子集合时会出现异常");
arrayList.add(12 + "");
// subList = arrayList.subList(5, 7);
System.out.println(JsonUtils.toJson(arrayList));
System.out.println(JsonUtils.toJson(subList));
}
// 输出如下信息:
["0","1","2","3","4","5","6","7","8","9"]
["5","6"]
父子集合做非结构性修改,正常输出
["0","1","2","3","4","11","12","7","8","9"]
["11","12"]
子集合做结构性修改,影响父集合,正常输出(注意13插入的位置)
["0","1","2","3","4","11","12","13","7","8","9"]
["11","12","13"]
父集合做结构性修改,子集合失效,访问子集合时会出现异常
["0","1","2","3","4","11","12","13","7","8","9","12"]
Exception in thread "main" java.util.ConcurrentModificationException
循环-forEach
- forEach(Consumer<? super E> action):循环(Consumer是函数式接口,可配合lamble使用)
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
// 调用函数式接口的实现
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
注意循环内不要对list进行修改(任何导致modCount改变的操作),会导致ConcurrentModificationException
// arrayList.add(1-10000);
arrayList.forEach(e-> {
System.out.println(e);
if (e.equals("5000")){
arrayList.remove("5000");
}
});
其他方法
- 实例的容量调整为列表的当前大小,减少空间浪费
public void trimToSize(){}
- 判断是否需要扩容(添加元素时系统会自动扩容,没必要主动调用这个方法)
public void ensureCapacity(int minCapacity){}
- 返回列表的当前大小
public int size(){}
- 查找元素,返回最后匹配的元素所在的索引值(使用equals比较,如果没匹配到,返回-1)
public int lastIndexOf(Object o){}
- 复制集合(元素本身不会复制)
public Object clone() {}
- 转换为数组
public Object[] toArray() {}
public <T> T[] toArray(T[] a) {}
- 获取指定索引的元素(底层为数组,所以效率比较高)
public E get(int index) {}
- 替换元素(返回被替换的元素)
public E set(int index, E element){}
- 可分割迭代器(用到了内部类ArrayListSpliterator)
- 原理:对任务进行分割,每个线程执行一段,线程安全
public Spliterator<E> spliterator() {}
- 原理:对任务进行分割,每个线程执行一段,线程安全
- 排序(函数式接口)
public void sort(Comparator<? super E> c) {}
- 迭代
- (用到内部类Itr)
public Iterator<E> iterator() {}
- 用到了内部类ListItr
public ListIterator<E> listIterator() {}
- (用到内部类Itr)
- 判断是否为空(先判断!=null,再调用该方法)
public boolean isEmpty(){}
ArrayList总结(基于1.8)
- 原理
- 基于数组,封装了一个动态、再分配的Object[]数组
- 容量
- 默认初始化长度:
DEFAULT_CAPACITY = 10
- 1.5倍扩容:
oldCapacity + (oldCapacity >> 1)
- 扩容原理是创建新数组,copy元素,所以在初始化数组时,在数据长度已知或有范围时,尽量指定初始化大小,减少扩容次数,避免时间、空间浪费
- 默认初始化长度:
- 线程安全性
- 线程不安全,若需要线程安全的数组,可用Collicitons工具类
List<String> testList = Collections.synchronizedList(new ArrayList<>());
- 同样实现List类的Vector是线程安全的,但不推荐使用,其线程安全原理是使用synchronized同步块,效率低
- 线程不安全,若需要线程安全的数组,可用Collicitons工具类
- arrayList允许存放null
- arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除时性能会下降,需要移动数据位置
- remove操作会将后续元素向前移位
- remove某个对象时,使用
equals
方法来匹配目标值的 - retainAll用来取两个集合的交集,removeAll是移除交集
- 使用subList时,要注意父集合结构性修改导致的子集合时效问题,最好随用随取