上一篇文章 java8 ArrayList源码阅读已经分析了ArrayList源码,现对ArrayList做个小结。
ArrayList一个动态数组,其本质也是用数组实现的,它具有:随机访问速度快,插入和移除性能较差(数组的特点);支持null元素;有顺序;元素可以重复;线程不安全;
ArrayList实现了List接口以及list相关的所有方法,它允许所有元素的插入,包括null。另外,ArrayList和Vector除了线程不同步之外,大致相等。
- ArrayList继承AbstractList并实现了List接口,RandomAccess接口,Cloneable接口,Serializable接口,因此它支持快速访问,可复制,可序列化。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- ArrayList 默认初始化大小为0(不是10)
//在以前的版本,ArrayList 默认初始化大小为10,其默认构造函数为
public ArrayList() {
this(10);
}
//jdk8版本,默认构造方法,使用空数组初始化,容量大小默认为0
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA; //EMPTY_ELEMENTDATA={}
}
- ArrayList内部数组的扩容:
当向ArrayList添加新元素前,会调用ensureCapacityInternal(int)方法来保证内部数组空间足够。
//添加新元素时使用,使用指定参数设定最小容量,minCapacity为size+1
private void ensureCapacityInternal(int minCapacity) {
//如果数组为空,使用默认值和参数中较大者作为容量预设值
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//根据指定参数增加数组容量
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //上面提到,只要涉及数组结构的改变(这里是数组大小改变),该变量改变
//如果参数大于数组容量,就增加数组容量
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//增加容量,以确保它可以至少持有由参数指定的元素的数目
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//新容量为原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果仍比参数小,则新容量大小取参数的值
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//若新容量大于默认的最大值,则检查是否溢出
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
从grow()方法可以看出,如果内部数组空间不够,ArrayList每次扩容都是扩1.5倍,然后调用Arrays类的copyOf方法,把元素重新拷贝到一个新的数组中去。扩1.5倍的原因也好理解,因为扩容本质最后是将元素全部重新拷贝到一个新的数组,是耗时的,所以如果每次add都需要扩容一次,那效率是非常低的。所以预存一定空间以供add使用。
- ArrayList内部数组的收缩:
利用trimToSize()方法,由于数组扩容时是扩大1.5倍,因此通常会出现容量大于实际有效元素数量,可以将数组占用内存大小压缩到数组中元素个数,可以在内存紧张时调用。同样也是通过Arrays.copyof方法将元素拷贝到一片新的内寸空间,老的空间由gc回收。
//释放数组空余的空间,容量为数组实际元素数量(因为容量通常会大于实际元素数量,
public void trimToSize() {
modCount++; //涉及数组的改变,modCount都会改变,留意该变量,后面会频繁出现
if (size < elementData.length) { //如果数组实际元素数量比数组容量小,则重新拷贝到一片新的内寸空间
elementData = Arrays.copyOf(elementData, size);
}
}
- indexOf(Object o)
该方法会根据是否为null使用不同方式判断。如果是元素为null,则直接比较地址,否则使用equals的方法比较,加快比较效率。lastIndexOf(Object o) 同理。
//返回特定元素在数组首次出现的位置(遍历的方式),会根据是否为null使用不同方式判断。不存在就返回-1
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; //不存在返回-1
}
- clone() 浅拷贝
//返回副本,浅拷贝
//复制过程数组发生改变会抛出异常
public Object clone() {
try {
//java.lang.Object.clone()只是一种浅复制,所以,v的elementData引用的还是当前ArrayList的elementData的引用
java.util.ArrayList<?> v = (java.util.ArrayList<?>) super.clone();
//对原来ArrayList的elementData进行一个数组拷贝,然后赋值给v的elementData
//这样,v的elementData就是原先ArrayList的elementData的一个副本,不再是内存中同一块数组
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
举个例子说明:
public class Main {
static class A {
int a;
A(int a) {
this.a = a;
}
@Override
public String toString() {
return super.toString() + " : " + a;
}
}
public static void main(String[] args) {
ArrayList<A> a1=new ArrayList<>();
a1.add(new A(1));
a1.add(new A(2));
ArrayList<A> a2= (ArrayList<A>) a1.clone();
System.out.println(a1);
System.out.println("---");
System.out.println(a1);
System.out.println("---");
a2.get(1).a=100;
System.out.println(a1);
System.out.println("---");
System.out.println(a1);
System.out.println("---");
}
}
/*
* 输出结果:
[Main$A@1b6d3586 : 1, Main$A@4554617c : 2]
---
[Main$A@1b6d3586 : 1, Main$A@4554617c : 2]
---
[Main$A@1b6d3586 : 1, Main$A@4554617c : 100]
---
[Main$A@1b6d3586 : 1, Main$A@4554617c : 100]
---
*/
从上述输出结果可以看出,尽管克隆的数组不是跟原数组同一块内存,但内容引用是同样的,指向同一个地址,可以从输出结果看出,因此改变克隆后数组的元素内容,相应的原数组内容发生相应变化,因此clone()方法是浅拷贝。
(注:对于基本类型,如int,则不会发生这种情况。)
add(),addAll(),remove(),removeRange()
关于对数组元素的添加或删除,其实现均是用System.arraycopy()来移动的。
void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);removeAll 和 retainAll
removeAll 是删除指定集合的元素,retainAll则是仅保留指定集合的元素。
//部分应用场景:
//并集
list1.addAll(list2);
//交集
list1.retainAll(list2);
//差集
list1.removeAll(list2);
//无重复并集
list2.removeAll(list1);
list1.addAll(list2);
而它们背后的实现则比较巧妙。
//删除指定集合的元素
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c); //检查集合是否为空
return batchRemove(c, false); //调用batchRemove,complement为false
}
//与removeAll相反,仅保留指定集合的元素
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c); //检查集合是否为空
return batchRemove(c, true); //调用batchRemove,complement为true
}
//complement true时从数组保留指定集合中元素的值,为false时从数组删除指定集合中元素的值。
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0; //w为数组更新后的大小
boolean modified = false;
try {
//遍历数组,并检查元素是否在指定集合中,根据complement的值保留特定值到数组
//若complement为true即保留,则将相同元素移动到数组前端
//若complement为false即删除,则将不同元素移动到数组前端
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
//如果r!=size则说明c.contains(elementData[r])抛出异常
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;
}
可以看出它们的实现类似于jvm中垃圾回收机制中的整理-清除方法,即先将需要保留的元素紧凑地移到一块儿,剩下的即为可以删除的元素,然后剩下的元素置null。
- modCount变量[fail-fast机制]
记录了数组的修改次数,在ArrayList的所有涉及结构变化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。这些方法每调用一次,modCount的值就加1。
该变量迭代器等方面体现。
//返回普通迭代器
public Iterator<E> iterator() {
return new java.util.ArrayList.Itr();
}
java.util.ArrayList.Itr()是ArrayList的一个内部类,其包含了一个int型的属性:expectedModCount,这个属性在Itr类初始化时被赋予ArrayList对象的modCount属性的值。该内部类的每个方法都涉及到checkForComodification()方法。
//检查数组是否修改,根据expectedModCount和modCount判断
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
因此在使用迭代器迭代过程中,不允许对数组结构做修改,否则会抛出异常 java.util.ConcurrentModificationException。
举例如下:
public class Main {
static class A {
int a;
A(int a) {
this.a = a;
}
@Override
public String toString() {
return super.toString() + " : " + a;
}
}
public static void main(String[] args) {
ArrayList<A> a1=new ArrayList<>();
a1.add(new A(1));
a1.add(new A(2));
Iterator i=a1.iterator();
System.out.println(i.next()+"");
a1.get(1).a=100; //ok
a1.add(new A(3)); //error
System.out.println(i.next()+"");
}
}
/*
* 输出为:
Main$A@1b6d3586 : 1
Exception in thread "main" java.util.ConcurrentModificationException
*/
- forEach() java8新增方法
//遍历每个元素做指定操作,java8新增方法
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action); //检查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();
}
}
java8前,遍历集合通常使用for循环来遍历,比如遍历输出:
for(A a:aList){
System.out.println(a);
}
现在可以采用
// lambda
aList.foreach(a -> System.out.println(a);
// or
aList.forEach(new Consumer<A>() {
@Override
public void accept(A a) {
System.out.println(a);
}
});
spliterator() java8新增方法
类似Iiterator,用于多线程,仍需学习。removeIf() java8新增方法
其实现有点类似batchRemove()的实现方式,先将需要保留的元素紧凑地移到一块儿,剩下的即为可以删除的元素。同时还利用了BitSet来记录要删除元素的下标。
//判断条件是否满足,满足则删除,java8新增方法
@Override
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter); //检查filter是否为空
int removeCount = 0; //记录要删除的数目
final BitSet removeSet = new BitSet(size); //利用BitSet来记录删除元素的下标
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];
//如果当前元素符合条件,则BitSet标记当前下标
if (filter.test(element)) {
removeSet.set(i);
removeCount++;
}
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
final boolean anyToRemove = removeCount > 0; //根据removeCount变量判断是否有满足条件的元素
if (anyToRemove) {
final int newSize = size - removeCount; //删除后数组元素的个数
//遍历数组,将不满足的元素前移到数组前端
for (int i = 0, j = 0; (i < size) && (j < newSize); i++, j++) {
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;
}
- replaceAll() java8新增方法
//根据operator进行替换,java8新增方法
@Override
@SuppressWarnings("unchecked")
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final int expectedModCount = modCount;
final int size = this.size;
for (int i = 0; modCount == expectedModCount && i < size; i++) {
elementData[i] = operator.apply((E) elementData[i]);
}
//检查数组是否修改
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
- sort() java8新增方法
//根据Comparator排序,java8新增方法
@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++;
}
java8之前,集合排序要用Collections.sort()方法,现在可以直接用自带的sort方法了。
// lambda
aList.sort((a,b)->(a.a-b.b));
// or
a1.sort(new Comparator<A>() {
@Override
public int compare(A o1, A o2) {
return o1.a-o2.a;
}
});